Section 0: Answers to questions

Question 1

Final Prediction Model (Decision Tree):

# Question 1
# Final Prediction Model (Decision Tree):

require(RCurl)
sit = getURLContent('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', binary=TRUE, followlocation = TRUE, ssl.verifypeer = FALSE)
con = gzcon(rawConnection(sit, 'rb'))
source(con)
close(con)

## Get data, this is original file downloaded without any changes
setwd("~/Documents/interview_prep/Question1")
data_in <- read.csv(file = 'DataSet.csv')
date_ts <- as.Date(data_in$DATE, format = "%m/%d/%Y")
library(xts)
marketclose_xts <- xts(data_in$MARKETCLOSE,order.by = date_ts)
marketclose_ret <- 100*diff(log(marketclose_xts))

#Estimate historical relative volatility
library(quantmod)
ret.log <- ROC(marketclose_xts, type='continuous')
hist.vol <- runSD(ret.log, n = 21)
vol.rank <- percent.rank(SMA(percent.rank(hist.vol, 252), 21), 250)

## Mean reversion feature
rsi2_values <- RSI(marketclose_xts,2)

## Trend following feature
sma.short <-  SMA(marketclose_xts, 5)
sma.long <- SMA(marketclose_xts, 20)

sig_opt_optimal <- Lag(iif(vol.rank > 0.2, 
                           iif(rsi2_values < 36  , 1, -1),
                           iif(sma.short < sma.long | rsi2_values > 80, -1, 1)
))
ret_opt_optimal <- 1000*momentum(marketclose_xts)*sig_opt_optimal
# Verify sig_opt_optimal=1 gives ret_opt_optimal= market pnl
# it seems momentum function divides the difference by 10
eq_opt_optimal <- (cumsum(ret_opt_optimal[-c(1)]))

marketclose_weekday <- base::weekdays(date_ts,abbreviate=TRUE)
## Modeling Monday returns
marketclose_ret_Mon <- marketclose_ret[marketclose_weekday == "Mon"]
## Monday returns clearly show a visible pattern, we can create a time series for returns and use its predictability to improve the earlier forecast
## Switching strategy based on Monday returns:

## Mean reversion
rsi2_values_Mon = RSI(marketclose_ret_Mon/100,2)

## Trend following
sma.short_Mon <- SMA(marketclose_ret_Mon/100, 2)
sma.long_Mon <-  SMA(marketclose_ret_Mon/100, 5)
library(quantmod)
ret.log_Mon = marketclose_ret_Mon/100
hist.vol_Mon = runSD(ret.log_Mon, n = 5)
vol.rank_Mon = percent.rank(SMA(percent.rank(hist.vol_Mon, 52), 5), 50)

sig_opt.Mon <-  Lag(Lag(iif(vol.rank_Mon > 0.44, 
                            iif((rsi2_values_Mon < 62) , 1, -1),
                            iif(sma.short_Mon < sma.long_Mon | ((rsi2_values_Mon > 60)), -1, 1)
)))

sig_opt_optimal.MonCorrection <- sig_opt_optimal
sig_opt_optimal.MonCorrection[marketclose_weekday == "Mon"][-c(1,2)] <- sig_opt.Mon[-c(1,2)]

ret_opt_optimal.MonCorrection <- 1000*momentum(marketclose_xts)[-c(1,2)]*sig_opt_optimal.MonCorrection
eq_opt_optimal.MonCorrection <- as.numeric( cumsum( (ret_opt_optimal.MonCorrection) ))

lastday_row <- length(marketclose_ret)

prediction_opt <- iif(vol.rank[lastday_row] > 0.2, 
                      iif(rsi2_values[lastday_row] < 36  , 1, -1),
                      iif(sma.short[lastday_row] < sma.long[lastday_row] | rsi2_values > 80, -1, 1)
)

# Predicts -1
print(paste0("Prediction for next day:  ",prediction_opt))
[1] "Prediction for next day:  -1"

Question 2

Model cumulative PNL vs market PNL



plot(c(NA,eq_opt_optimal.MonCorrection)~date_ts[-c(1)],col="red",type='l', ylim=c(0,140000),xlab= "Year", ylab = "Cumulative PNL")
lines(coredata(eq_opt_optimal) ~ date_ts[-c(1)],col="blue")
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="black")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("Model Cumulative PNL (Monday trend correction) Backtesting","Model Cumulative PNL: Backtesting","Market Cumulative PNL"),
       fill=c("red","blue","black"))

Model Development

Section I: Data Cleaning (Python)

# This snippet is written in python3
# import pandas as pd
# import numpy as np
# df = pd.read_csv('DataSet.csv')
# 
# for column in df.columns:
#   print(column)
#   print(df[df[column].isnull()].index.tolist())
# 
# df['FEATURE_3'] = df['FEATURE_3'].str.replace('%',"")
# df['DATE'] = pd.to_datetime(df['DATE'], format="%m/%d/%Y")
#
# #### We can use a dictionary to map the string pattern to nan
# #### cleaning_dict = {'^#.*': np.nan}
# #### df['FEATURE_3'] = df['FEATURE_3'].replace(cleaning_dict, regex=True)
# #### df['BINARY_FEATURE'] = df['BINARY_FEATURE'].replace(cleaning_dict, regex=True)
# # OR Using errors='coerce' will replace all non-numeric values with NaN
# df['BINARY_FEATURE'] = pd.to_numeric(df['BINARY_FEATURE'],errors='coerce')
# df['FEATURE_3'] = pd.to_numeric(df['FEATURE_3'],errors='coerce')
# #### Drop all nan
# df_rmna = df.dropna()
# #### Save file
# df_rmna.to_csv('DataSet_rmNA.csv')

Section II: Data Exploration (R)

setwd("~/Documents/interview_prep/Question1")
data_in <- read.csv(file = 'DataSet.csv')
date_ts <- as.Date(data_in$DATE, format = "%m/%d/%Y")
marketclose_ts <- ts(data_in$MARKETCLOSE, start=date_ts[1])
#print(marketclose_ts)
plot.ts(marketclose_ts)

library(xts)
marketclose_xts <- xts(x=data_in$MARKETCLOSE,order.by = date_ts)
autoplot(marketclose_xts)

# Split marketclose by week and calculate averages per week
weekly_marketclose_xts <- split(marketclose_xts, f = "weeks")
print(head(weekly_marketclose_xts[[1]]))
             [,1]
2002-07-01 109.86
2002-07-02 109.76
2002-07-03 109.83
print(head(weekly_marketclose_xts[[2]]))
             [,1]
2002-07-08 109.18
2002-07-09 109.27
2002-07-10 109.80
2002-07-11 110.06
2002-07-12 110.42
dates_weekly_endpoints <- xts::endpoints(marketclose_xts,on='weeks')
readings_per_week <- dates_weekly_endpoints[-c(1)]-dates_weekly_endpoints[-c(length(dates_weekly_endpoints))]

# print weeks with less than 5 values per week
for (weekID in 1:length(weekly_marketclose_xts)) {
  if (length(weekly_marketclose_xts[[weekID]][,1]) < 5) {
    week_len <- length(weekly_marketclose_xts[[weekID]])
    week_endpoint_ID <- dates_weekly_endpoints[1+weekID]
    print(paste("WeekID:",weekID,", num_readings:",week_len))
    print(paste("Week_start:",as.character(date_ts[week_endpoint_ID-week_len+1]),
    ", Week_end:",as.character(date_ts[week_endpoint_ID]) ) )
  }
}
[1] "WeekID: 1 , num_readings: 3"
[1] "Week_start: 2002-07-01 , Week_end: 2002-07-03"
[1] "WeekID: 10 , num_readings: 4"
[1] "Week_start: 2002-09-03 , Week_end: 2002-09-06"
[1] "WeekID: 22 , num_readings: 3"
[1] "Week_start: 2002-11-25 , Week_end: 2002-11-27"
[1] "WeekID: 26 , num_readings: 4"
[1] "Week_start: 2002-12-23 , Week_end: 2002-12-27"
[1] "WeekID: 27 , num_readings: 4"
[1] "Week_start: 2002-12-30 , Week_end: 2003-01-03"
[1] "WeekID: 30 , num_readings: 4"
[1] "Week_start: 2003-01-21 , Week_end: 2003-01-24"
[1] "WeekID: 34 , num_readings: 4"
[1] "Week_start: 2003-02-18 , Week_end: 2003-02-21"
[1] "WeekID: 42 , num_readings: 4"
[1] "Week_start: 2003-04-14 , Week_end: 2003-04-17"
[1] "WeekID: 48 , num_readings: 4"
[1] "Week_start: 2003-05-27 , Week_end: 2003-05-30"
[1] "WeekID: 53 , num_readings: 4"
[1] "Week_start: 2003-06-30 , Week_end: 2003-07-03"
[1] "WeekID: 62 , num_readings: 4"
[1] "Week_start: 2003-09-02 , Week_end: 2003-09-05"
[1] "WeekID: 74 , num_readings: 3"
[1] "Week_start: 2003-11-24 , Week_end: 2003-11-26"
[1] "WeekID: 78 , num_readings: 3"
[1] "Week_start: 2003-12-22 , Week_end: 2003-12-24"
[1] "WeekID: 79 , num_readings: 3"
[1] "Week_start: 2003-12-29 , Week_end: 2003-12-31"
[1] "WeekID: 82 , num_readings: 4"
[1] "Week_start: 2004-01-20 , Week_end: 2004-01-23"
[1] "WeekID: 86 , num_readings: 4"
[1] "Week_start: 2004-02-17 , Week_end: 2004-02-20"
[1] "WeekID: 101 , num_readings: 4"
[1] "Week_start: 2004-06-01 , Week_end: 2004-06-04"
[1] "WeekID: 102 , num_readings: 4"
[1] "Week_start: 2004-06-07 , Week_end: 2004-06-10"
[1] "WeekID: 106 , num_readings: 4"
[1] "Week_start: 2004-07-06 , Week_end: 2004-07-09"
[1] "WeekID: 115 , num_readings: 4"
[1] "Week_start: 2004-09-07 , Week_end: 2004-09-10"
[1] "WeekID: 126 , num_readings: 3"
[1] "Week_start: 2004-11-22 , Week_end: 2004-11-24"
[1] "WeekID: 130 , num_readings: 4"
[1] "Week_start: 2004-12-20 , Week_end: 2004-12-23"
[1] "WeekID: 131 , num_readings: 4"
[1] "Week_start: 2004-12-27 , Week_end: 2004-12-30"
[1] "WeekID: 134 , num_readings: 4"
[1] "Week_start: 2005-01-18 , Week_end: 2005-01-21"
[1] "WeekID: 139 , num_readings: 4"
[1] "Week_start: 2005-02-22 , Week_end: 2005-02-25"
[1] "WeekID: 143 , num_readings: 4"
[1] "Week_start: 2005-03-21 , Week_end: 2005-03-24"
[1] "WeekID: 153 , num_readings: 4"
[1] "Week_start: 2005-05-31 , Week_end: 2005-06-03"
[1] "WeekID: 158 , num_readings: 4"
[1] "Week_start: 2005-07-05 , Week_end: 2005-07-08"
[1] "WeekID: 167 , num_readings: 4"
[1] "Week_start: 2005-09-06 , Week_end: 2005-09-09"
[1] "WeekID: 178 , num_readings: 3"
[1] "Week_start: 2005-11-21 , Week_end: 2005-11-23"
[1] "WeekID: 183 , num_readings: 4"
[1] "Week_start: 2005-12-27 , Week_end: 2005-12-30"
[1] "WeekID: 184 , num_readings: 4"
[1] "Week_start: 2006-01-03 , Week_end: 2006-01-06"
[1] "WeekID: 186 , num_readings: 4"
[1] "Week_start: 2006-01-17 , Week_end: 2006-01-20"
[1] "WeekID: 191 , num_readings: 4"
[1] "Week_start: 2006-02-21 , Week_end: 2006-02-24"
[1] "WeekID: 198 , num_readings: 4"
[1] "Week_start: 2006-04-10 , Week_end: 2006-04-13"
[1] "WeekID: 205 , num_readings: 4"
[1] "Week_start: 2006-05-30 , Week_end: 2006-06-02"
[1] "WeekID: 210 , num_readings: 3"
[1] "Week_start: 2006-07-05 , Week_end: 2006-07-07"
[1] "WeekID: 235 , num_readings: 4"
[1] "Week_start: 2006-12-26 , Week_end: 2006-12-29"
[1] "WeekID: 236 , num_readings: 4"
[1] "Week_start: 2007-01-02 , Week_end: 2007-01-05"
weekly_avg_marketclose <- lapply(X = weekly_marketclose_xts, function(X){return(mean(X))})
all_weeks <- ts(unlist(weekly_avg_marketclose))
filtered_weeks <- all_weeks
filtered_weeks[readings_per_week<5] <- NaN
#filtered_weeks <- ts(unlist(weekly_avg_marketclose)[readings_per_week==5])
closes <- cbind(all_weeks, filtered_weeks)
autoplot(closes)

#autoplot(ts(unlist(weekly_avg_marketclose)))

It would be interesting to see effects of holidays/long weekends, annual events (eg superbowl, quad witching). We will ignore them for now. Other factors include sector specific sentiment or whole market sentiment, which hopefully decomposition below will uncover.

Number of daily readings by year:


table(format(date_ts,"%Y"))

2002 2003 2004 2005 2006 2007 
 126  250  250  251  252   44 

Number of weekly readings by year:

table(format(date_ts[dates_weekly_endpoints[-c(1)]],"%Y"))

2002 2003 2004 2005 2006 2007 
  26   53   52   52   52    9 

Number of trading days per trading week (avg) by year

table(format(date_ts,"%Y"))/table(format(date_ts[dates_weekly_endpoints[-c(1)]],"%Y"))

    2002     2003     2004     2005     2006     2007 
4.846154 4.716981 4.807692 4.826923 4.846154 4.888889 

Number of trading days per month by year

dates_monthly_endpoints <- xts::endpoints(marketclose_xts,on='months')
table(format(date_ts[dates_monthly_endpoints[-c(1)]],"%Y"))

2002 2003 2004 2005 2006 2007 
   6   12   12   12   12    3 
table(format(date_ts,"%Y"))/table(format(date_ts[dates_monthly_endpoints[-c(1)]],"%Y"))

    2002     2003     2004     2005     2006     2007 
21.00000 20.83333 20.83333 20.91667 21.00000 14.66667 
table(format(date_ts,"%m/%Y",start=("07/2002")))

01/2003 01/2004 01/2005 01/2006 01/2007 02/2003 02/2004 02/2005 02/2006 02/2007 03/2003 
     21      19      20      20      22      19      19      19      19      20      21 
03/2004 03/2005 03/2006 03/2007 04/2003 04/2004 04/2005 04/2006 05/2003 05/2004 05/2005 
     23      22      23       2      21      22      21      19      21      20      21 
05/2006 06/2003 06/2004 06/2005 06/2006 07/2002 07/2003 07/2004 07/2005 07/2006 08/2002 
     22      21      21      22      22      21      22      21      20      19      22 
08/2003 08/2004 08/2005 08/2006 09/2002 09/2003 09/2004 09/2005 09/2006 10/2002 10/2003 
     21      22      23      23      20      21      21      21      21      23      23 
10/2004 10/2005 10/2006 11/2002 11/2003 11/2004 11/2005 11/2006 12/2002 12/2003 12/2004 
     21      21      22      19      18      20      20      22      21      21      21 
12/2005 12/2006 
     21      20 
mean(table(format(date_ts,"%m/%Y")))
[1] 20.57895
table(format(date_ts,"%Y"))

2002 2003 2004 2005 2006 2007 
 126  250  250  251  252   44 

Last reading date for each year:

date_ts[xts::endpoints(marketclose_xts,on='years')[-c(1)]]
[1] "2002-12-31" "2003-12-31" "2004-12-30" "2005-12-30" "2006-12-29" "2007-03-02"

Year 2003,04,05,06 seasonal trends:

weekly_avg_marketclose_2003456  <- ts(as.numeric(unlist(weekly_avg_marketclose))[(26+1):(26+53+52+52+52)],frequency=52)
#library(forecast)
no_transform_2003456 <- stl(log(weekly_avg_marketclose_2003456), s.window=53)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
autoplot(no_transform_2003456)

#TimeSeriesWeeklyDecomposed<-stl(ts_data , s.window="periodic")

Seasonal trends:

library(forecast)
This is forecast 8.16 
  Need help getting started? Try the online textbook FPP:
  http://otexts.com/fpp2/
no_transform_2003456 %>% seasonal() %>% ggsubseriesplot()

no_transform_2003456 %>% remainder() %>% autoplot()

rm(list=ls())
setwd("~/Documents/interview_prep/Question1")
data_in <- read.csv(file = 'DataSet.csv')
date_ts <- as.Date(data_in$DATE, format = "%m/%d/%Y")
marketclose_ts <- ts(data_in$MARKETCLOSE, start=date_ts[1],frequency = 251.625)
plot.ts(marketclose_ts)

Given the irregular nature of the time series we use xts modeling and estimate daily returns %


library(xts)
marketclose_xts <- xts(x=data_in$MARKETCLOSE,order.by = date_ts)
marketclose_ret <- 100*diff.xts(log(marketclose_xts))
#marketclose_ret <- as.zoo(marketclose_ret[-c(1)])
plot.zoo(marketclose_ret, ylab = "Daily returns (%)", main = "Percentage daily returns")
# lowess fit with the f = 1/average # trading days in a month
# Calculation for 20.8 follow below
lines(index(marketclose_ret), lowess(marketclose_ret[,1], f = 1/20.8)$y, col = "red", lwd = 2)

# Volatility as function of month
plot(jitter(as.numeric(format(index(marketclose_ret),"%m")), amount = 1/3), marketclose_ret, pch = 20, ylab = "Daily percent returns", xlab = "Month", bty = "l")

boxplot(as.vector(marketclose_ret) ~ factor(quarters(index(marketclose_ret),abbreviate = TRUE),labels=c("Q3","Q4","Q1","Q2")), xlab = "Quarter", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

boxplot(as.vector(marketclose_ret) ~ factor(months(index(marketclose_ret),abbreviate = TRUE),     levels = c("Jul","Aug","Sep","Oct","Nov","Dec","Jan","Feb","Mar","Apr","May","Jun")), xlab = "Month", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),
        levels = c("Mon","Tue","Wed","Thu","Fri")) ,xlab = "Day of the week", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

myplot <- boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))*factor(months(index(marketclose_ret),abbreviate = TRUE),levels = c("Jul","Aug","Sep","Oct","Nov","Dec","Jan","Feb","Mar","Apr","May","Jun")),
xlab = "Day of the week in given month", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

myplot <- boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))*factor(quarters(index(marketclose_ret),abbreviate = TRUE),levels = c("Q3","Q4","Q1","Q2")),
xlab = "Day of the week in given year", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

myplot <- boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))*factor(format(index(marketclose_ret),"%Y"),levels = c("2002","2003","2004","2005","2006","2007")),
xlab = "Day of the week in given year", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

Monday returns as function of return on friday return in prev week by year

#head(which(factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))=="Mon"))
#head(which(factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))=="Fri"))


monday_indices <- which(weekdays(index(marketclose_ret),abbreviate = TRUE)=="Mon")
monday_indices <- monday_indices[-c(1)]
plot(as.vector(marketclose_ret[monday_indices])~as.vector(marketclose_ret[(monday_indices-1)]),
     xlab="Prev day change",ylab="Monday change")
lines(lowess(as.vector(marketclose_ret[monday_indices])~as.vector(marketclose_ret[(monday_indices-1)]),f=1/2),col="red")
grid(col=1,lwd=1.5)


plot.ts(as.vector(marketclose_ret[(monday_indices)]),ylim=c(-1.5,1.5),
     xlab="Date",ylab = "% Change")
#lines(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),col=2)
lines(lowess(as.vector(marketclose_ret[(monday_indices-1)]),f=1/2),col="green",type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices)]),f=1/2),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices-2)]),f=1/2),col="blue",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(marketclose_ret[(monday_indices-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices)])),f=1/2),col="black",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),f=1/2),col="orange",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-2)])),f=1/2),col="yellow",ylim=c(-1,1),type="o")
#lines(sign(as.numeric(marketclose_ret)[monday_indices]))
grid(col=1,lwd=1.5)

plot.ts(as.vector(marketclose_ret[(monday_indices)]),ylim=c(-1.5,1.5),
     xlab="Date",ylab = "% Change")
#lines(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),col=2)
lines(lowess(as.vector(marketclose_ret[(monday_indices-1)]),f=1/4),col="green",type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices)]),f=1/4),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices-2)]),f=1/4),col="blue",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(marketclose_ret[(monday_indices-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices)])),f=1/4),col="black",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),f=1/4),col="orange",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-2)])),f=1/2),col="yellow",ylim=c(-1,1),type="o")
#lines(sign(as.numeric(marketclose_ret)[monday_indices]))
grid(col=1,lwd=1.5)

plot.ts(as.vector(marketclose_ret[(monday_indices)]),ylim=c(-1.5,1.5),
     xlab="Date",ylab = "% Change")
#lines(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),col=2)
lines(lowess(as.vector(marketclose_ret[(monday_indices-1)]),f=1/8),col="green",type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices)]),f=1/8),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices-2)]),f=1/8),col="blue",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(marketclose_ret[(monday_indices-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices)])),f=1/8),col="black",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),f=1/8),col="orange",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-2)])),f=1/2),col="yellow",ylim=c(-1,1),type="o")
#lines(sign(as.numeric(marketclose_ret)[monday_indices]))
grid(col=1,lwd=1.5)

Even with f=1/8 it seems Monday daily returns will follow strictly downward trend

#tail(lowess(as.vector(marketclose_ret[c((monday_indices-1),1173)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices-2),1172)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices-3),1171)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices-4),1170)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices))]),f=1/8)$y,15)

plot(tail(lowess(as.vector(marketclose_ret[c((monday_indices-1),1173)]),f=1/8)$y,50),ylim=c(-1,1),type='l',main="trend 50 day returns by day of the week",ylab="% Change",xlab="day")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices-2),1172)]),f=1/8)$y,50),col="blue")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices-3),1171)]),f=1/8)$y,50),col="green")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices-4),1170)]),f=1/8)$y,50),col="orange")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices))]),f=1/8)$y,50),col="red")
grid(col=1,lwd=1.5)

plot(c(rep(0,50),rollmean(as.vector(marketclose_ret),50)),ylim=c(-1,1),type='l',main="50 & 200 day MA of daily returns",ylab="% Change",xlab="day",col="red")
lines(c(rep(0,200),rollmean(as.vector(marketclose_ret),200)),col="green")
#lines(as.vector(marketclose_ret))
lines(lowess(marketclose_ret[,1], f = 1/20.8)$y, lwd = 2)
#lines(rollmean(as.vector(marketclose_ret[c((monday_indices-2),1172)]),50),col="blue")
#lines(rollmean(as.vector(marketclose_ret[c((monday_indices-3),1171)]),50),col="green")
#lines(rollmean(as.vector(marketclose_ret[c((monday_indices-4),1170)]),50),col="orange")
#lines(rollmean(as.vector(marketclose_ret[monday_indices]),50),col="red")
grid(col=1,lwd=1.5)

plot(rollmean(as.vector(marketclose_ret[c((monday_indices-1),1173)]),50),ylim=c(-.25,.25),type='l',main="50 week MA of weekly returns by day of the week",ylab="% Change",xlab="day")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-2),1172)]),50),col="blue")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-3),1171)]),50),col="green")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-4),1170)]),50),col="orange")
lines(rollmean(as.vector(marketclose_ret[monday_indices]),50),col="red")
grid(col=1,lwd=1.5)

plot(rollmean(as.vector(marketclose_ret[c((monday_indices-1),1173)]),200),ylim=c(-.25,.25),type='l',main="200 week MA returns by day of the week",ylab="% Change",xlab="day")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-2),1172)]),200),col="blue")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-3),1171)]),200),col="green")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-4),1170)]),200),col="orange")
lines(rollmean(as.vector(marketclose_ret[monday_indices]),200),col="red")
grid(col=1,lwd=1.5)

monday return wrt prev monday

indices_2007 <- which(as.numeric(format(index(marketclose_ret),"%Y")) %in% c(2006,2007))
monday_indices_2007 <- monday_indices[monday_indices %in% indices_2007]
plot(as.vector(marketclose_ret[monday_indices_2007])~as.vector(marketclose_ret[(monday_indices_2007-1)]),
     xlab="Prev day change 2006-2007",ylab="Monday change 2006-2007")
lines(lowess(as.vector(marketclose_ret[monday_indices_2007])~as.vector(marketclose_ret[(monday_indices_2007-1)]),f=1/2),col="red")
grid(col=1,lwd=1.5)

#plot.zoo(xts(sign(as.numeric(marketclose_ret)[monday_indices_2007])*sign(as.numeric(marketclose_ret)[(monday_indices_2007-1)]),order.by = index(marketclose_ret)[monday_indices_2007]),main= "Sign of Monday change times sign of prev day")
#lines(xts(sign(as.numeric(marketclose_ret)[monday_indices_2007]),order.by = index(marketclose_ret)[monday_indices_2007]),col="red")
#lines(sign(as.numeric(marketclose_ret)[monday_indices_2007]))
plot(lowess(as.vector(marketclose_ret[(monday_indices_2007-1)]),f=1/2),col="green",ylim=c(-1,1),
     xlab="Date",ylab = "% Change")
lines(lowess(as.vector(marketclose_ret[(monday_indices_2007)]),f=1/2),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices_2007-2)]),f=1/2),col="blue",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices_2007-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(sign(as.numeric(marketclose_ret)[monday_indices_2007]))
grid(col=1,lwd=1.5)

Plot of features

setwd("~/Documents/interview_prep/Question1")
data_in_cleaned <- read.csv(file = 'DataSet_rmNA.csv')
date_ts_cleaned <- as.Date(data_in_cleaned$DATE, format = "%Y-%m-%d")
marketclose_ts_cleaned <- ts(data_in_cleaned$MARKETCLOSE, start=date_ts_cleaned[1],frequency = 251.625)
plot.ts(marketclose_ts_cleaned)

library(xts)
marketclose_xts_cleaned <- xts(x=data_in_cleaned$MARKETCLOSE,order.by = date_ts_cleaned)
marketclose_ret_cleaned <- 100*diff.xts(log(marketclose_xts_cleaned))
## marketclose_ret_cleaned <- marketclose_ret_cleaned[-c(1)]
#marketclose_ret <- as.zoo(marketclose_ret[-c(1)])
plot.zoo(marketclose_ret_cleaned, ylab = "Daily returns (%)", main = "Percentage daily returns")
# lowess fit with the f = 1/average # trading days in a month
# Calculation for 20.8 follow below
lines(index(marketclose_ret_cleaned), lowess(marketclose_ret_cleaned[,1], f = 1/20.8)$y, col = "red", lwd = 2)
feature1_xts <- xts(x=data_in_cleaned$FEATURE_1,order.by = date_ts_cleaned)
feature1_diff_xts <- diff.xts((feature1_xts))
## feature1_diff_xts <- feature1_diff_xts[-c(1)]
lines(index(feature1_diff_xts), lowess(feature1_diff_xts[,1], f = 1/20.8)$y, col = "blue", lwd = 2)
feature2_xts <- xts(x=data_in_cleaned$FEATURE_2,order.by = date_ts_cleaned)
feature2_diff_xts <- diff.xts((feature2_xts))
lines(index(feature2_diff_xts), lowess(feature2_diff_xts[,1], f = 1/20.8)$y, col = "green", lwd = 2)
## feature2_diff_xts <- feature2_diff_xts[-c(1)]

feature3_xts <- xts(x=data_in_cleaned$FEATURE_3,order.by = date_ts_cleaned)
feature3_scaled_xts <- ((feature3_xts))/100
lines(index(feature3_scaled_xts), lowess(feature3_scaled_xts[,1], f = 1/20.8)$y, col = "orange", lwd = 2)
## feature3_scaled_xts <-  feature3_scaled_xts[-c(1)]

binaryf_xts <- xts(x=data_in_cleaned$BINARY_FEATURE,order.by = date_ts_cleaned)
## binaryf_xts <- binaryf_xts[-c(1)]
lines(index(binaryf_xts),binaryf_xts, col = "yellow", lwd = 2)

Cross corr between time seriers

title_logret <- "Daily log returns (%)"
par(mfrow = c(2, 2))
TSA::acf(marketclose_ret_cleaned, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(marketclose_ret_cleaned, na.action = na.pass,lag.max = 100, main = "")


# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Daily abs log returns (%)"
TSA::acf(abs(marketclose_ret_cleaned), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(marketclose_ret_cleaned), na.action = na.pass, lag.max = 100, main = "")

# (P)ACF of squared daily returns
#par(mfrow = c(1, 2))
#TSA::acf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
#pacf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
title_logret <- "Feature1 log returns"
par(mfrow = c(2, 2))
TSA::acf(feature1_diff_xts, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(feature1_diff_xts, na.action = na.pass,lag.max = 100, main = "")


# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Feature1 abs log returns"
TSA::acf(abs(feature1_diff_xts), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(feature1_diff_xts), na.action = na.pass, lag.max = 100, main = "")

title_logret <- "Feature2 log returns"
par(mfrow = c(2, 2))
TSA::acf(feature2_diff_xts, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(feature2_diff_xts, na.action = na.pass,lag.max = 100, main = "")


# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Feature2 abs log returns"
TSA::acf(abs(feature2_diff_xts), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(feature2_diff_xts), na.action = na.pass, lag.max = 100, main = "")

title_logret <- "Feature3 log returns"
par(mfrow = c(2, 2))
TSA::acf(feature3_scaled_xts, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(feature3_scaled_xts, na.action = na.pass,lag.max = 100, main = "")


# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Feature3 abs log returns"
TSA::acf(abs(feature3_scaled_xts), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(feature3_scaled_xts), na.action = na.pass, lag.max = 100, main = "")

Plot ret vs f1 f2 f3

par(mfrow =c(1,3) )

marketclose_ret_cleaned_ <- marketclose_ret_cleaned[-c(1)]
feature1_diff_xts_ <- feature1_diff_xts[-c(1)]
plot(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature1_diff_xts_) ,xlab="Feature1",
     ylab="Daily % log returns")
lines(lowess(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature1_diff_xts_), f=0.1),
      col="red")

feature2_diff_xts_ <- feature2_diff_xts[-c(1)]
plot(as.numeric(marketclose_ret_cleaned) ~ as.numeric(feature2_diff_xts) ,xlab="Feature2",
     ylab="Daily % log returns")
lines(lowess(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature2_diff_xts_), f=0.1),
      col="red")

feature3_scaled_xts_ <- feature3_scaled_xts[-c(1)]
plot(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature3_scaled_xts_) ,xlab="Feature3",
     ylab="Daily % log returns")
lines(lowess(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature3_scaled_xts_), f=0.1),
      col="red")

sel_fit <- auto.arima(marketclose_ret_cleaned_, xreg = cbind(feature2_diff_xts_  ,feature3_scaled_xts_))
plot(resid(sel_fit))

Box.test(resid(sel_fit), lag=100, type="Ljung")

    Box-Ljung test

data:  resid(sel_fit)
X-squared = 187.87, df = 100, p-value = 2.508e-07
qqnorm(resid(sel_fit, type="innovation"))

marketclose_ts <- ts(as.numeric(data_in_cleaned$MARKETCLOSE), start=date_ts_cleaned[1],frequency = 251.625)
marketclose_ts_decomp <- stl(log(marketclose_ts), s.window=21, robust=TRUE)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
forecast::autoplot(marketclose_ts_decomp)

#TimeSeriesWeeklyDecomposed<-stl(ts_data , s.window="periodic")
feature1_ts <- ts(as.numeric(feature1_xts), start=date_ts[1],frequency = 251.625)
feature1_ts_decomp <- stl(log(feature1_ts), s.window="periodic", robust=TRUE)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
autoplot(feature1_ts_decomp)

feature2_ts <- ts(as.numeric(feature2_xts), start=date_ts[1],frequency = 251.625)
feature2_ts_decomp <- stl(log(feature2_ts), s.window="periodic", robust=TRUE)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
autoplot(feature2_ts_decomp)

#monthplot(marketclose_ts_decomp,choice="seasonal")
#plot(seasonal(feature1_ts_decomp))
#lines(seasonal(marketclose_ts_decomp),col=2)
plot(forecast::remainder(marketclose_ts_decomp))
feature1_ts <- ts(data_in_cleaned$FEATURE_1,start = date_ts_cleaned[1],frequency = 251.625)
#lines(-(feature1_ts)/100,col=2)
feature2_ts <- ts(data_in_cleaned$FEATURE_2,start = date_ts_cleaned[1],frequency = 251.625)
#lines(-(feature2_ts)/100,col="green")
feature3_ts <- ts(data_in_cleaned$FEATURE_3,start = date_ts_cleaned[1],frequency = 251.625)
lines(((feature3_ts)/10000),col="blue")


#lines(((feature3_ts/1000))^{1/3}/10,col="blue")
#lines(remainder(marketclose_ts_decomp))

resid_decomp <- forecast::remainder(marketclose_ts_decomp)
resid_decomp_logret <- diff((resid_decomp))
title_logret <- "Daily log returns (%)"
par(mfrow = c(2, 2))
TSA::acf(resid_decomp_logret, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(resid_decomp_logret, na.action = na.pass,lag.max = 100, main = "")


# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Daily abs log returns (%)"
TSA::acf(abs(resid_decomp_logret), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(resid_decomp_logret), na.action = na.pass, lag.max = 100, main = "")

# (P)ACF of squared daily returns
#par(mfrow = c(1, 2))
#TSA::acf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
#pacf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
plot(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature2_ts)),xlim=c(-1,1))
lines(lowess(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature2_ts)), f=1/5),
      col="red")
grid(col=1,lwd=1.5)

plot(as.numeric(resid_decomp_logret) ~ as.numeric((feature3_ts)/100)[-c(1)],xlim=c(-1,1))
lines(lowess(as.numeric(resid_decomp_logret) ~ as.numeric((feature3_ts)/100)[-c(1)], f=1/5),
      col="red")
grid(col=1,lwd=1.5)

plot(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature1_ts)),xlim=c(-1,1))
lines(lowess(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature1_ts)), f=1/5),
      col="red")
grid(col=1,lwd=1.5)

for_stl <- forecast(marketclose_ts_decomp, h = 21)
plot(for_stl)

exp(for_stl$mean)
Time Series:
Start = 11884.6299056135 
End = 11884.7093889717 
Frequency = 251.625 
 [1] 137.1074 137.8653 137.4644 137.7676 138.6953 138.5616 139.3502 139.1598 138.8544
[10] 137.4894 136.3955 136.3485 136.0867 135.9724 136.1907 136.4010 137.3733 137.1660
[19] 137.2883 136.3747 136.3359
tail(marketclose_ts)
Time Series:
Start = 11884.6060606061 
End = 11884.6259314456 
Frequency = 251.625 
[1] 136.65 137.24 135.97 137.53 137.55 137.07

Section III: Model Development and analysis (R)

Daily Volatility based Trend Following and Mean Reversion Switching

(Please clear the environment and all variables, starting with a clean slate from here.)

I haven’t considered the provided features in the data for this model development since I don’t know what they represent, how they were extracted and whether or not they were lagged by one trading day. Further it seems all of them are endogenous (since they seem to be extracted from the price data), so they shouldn’t have any extra ‘information’ (in information theoretical sense of the word) even though they might be better predictors. However, here I focus on interpretability of the strategy in terms of variables well known in financial trading.

To enable interpretability of the model, I use well-defined features or features representing well-defined quantities related to a stock price eg. volatility, moving averages, relative strength index etc. The model essentially switches between following a trend and mean reversion based on the stock volatility (relative to its historical values).

The basic strategy is this: when the stock volatility is high relative to its usual volatility, I use mean reversion strategy based on RSI(2). When the volatility is low I follow the trend based on MA. In addition in slow volatility regime if the RSI(2) gets very high, this indicates oversold condition in which case I short. I further show improvement by updating the threshold parameters every 5 days by identifying best model parameters using grid search. Performance is judged on the basis of cumulative PNL wrt always buy strategy. The prediction for next also improves and is -1 from the updating strategy.

This strategy can be potentially further improved by using momentum, which I expect to be particularly strong during downturns. But it has to be balanced against being ‘too-late’ trying to time the stock price. Another potential improvement is to use volatility forecast using a Markov switching based GARCH model (since they seem to have been shown more accurate in predicting short term volatility <week than general GARCH models) or GARCH-ARIMA model. I use R here instead of python, however a small snippet of code to cleaup the data is in python3.

rm(list=ls())
## Setup
## Get some functions/packages
## install.packages("RCurl")
require(RCurl)
sit = getURLContent('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', binary=TRUE, followlocation = TRUE, ssl.verifypeer = FALSE)
con = gzcon(rawConnection(sit, 'rb'))
source(con)
close(con)
## Get data, this is original file downloaded without any changes
setwd("~/Documents/interview_prep/Question1")
data_in <- read.csv(file = 'DataSet.csv')
date_ts <- as.Date(data_in$DATE, format = "%m/%d/%Y")
library(xts)
marketclose_xts <- xts(data_in$MARKETCLOSE,order.by = date_ts)
marketclose_ret <- 100*diff(log(marketclose_xts))
#Estimate historical relative volatility
library(quantmod)
ret.log <- ROC(marketclose_xts, type='continuous')
hist.vol <- runSD(ret.log, n = 21)
vol.rank <- percent.rank(SMA(percent.rank(hist.vol, 252), 21), 250)
## Mean reversion feature
rsi2_values <- RSI(marketclose_xts,2)

## Trend following feature
sma.short <-  SMA(marketclose_xts, 5)
sma.long <- SMA(marketclose_xts, 20)
## Trading strategy: High Volatility - Mean reversion, Low volatility - follow trend
# long if vol.rank > 0.50 and rsi2_values < 50 (mean reversion) short otherwise
# short if vol.rank < 0.50 and 
# rsi2_values > 80 (overbought) or 5 week MA < 20 week MA
# long otherwise
# Add lag to predict next value from current estimates
# 
# `iif` is more stable version of `ifelse()` function in R to gracefully handle `NA` and `Inf` entries

sig <- Lag(iif(vol.rank > 0.50, 
               iif(rsi2_values < 50 , 1, -1),
               iif(sma.short < sma.long | rsi2_values > 80 , -1, 1)
))
## Evaluate
# We evaluate the strategy against always buy strategy in column CUMSUM in data. Momentum is just the difference of successive entries. Noting here that sig is delayed by 1 trading day.
print('CUMSUM column from data:')
print(head(data_in$CUMSUM[-c(1)]))
print("Always buy strategy")
ret <- 1000*momentum(marketclose_xts)*1
print(head(cumsum(ret[-c(1)])))
## Plot cumulative PNL
ret <- 1000*momentum(marketclose_xts)*sig
eq <- (cumsum(ret[-c(1)]))
plot(coredata(eq)~date_ts[-c(1)],type='l',xlab= "Year", ylab = "Cumulative PNL")
grid(col=1,lwd=1)
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
legend(x="topleft",legend=c("Volatility based switching","Buy Daily (Given)"),
       fill=c("black","red"))
## Compare cumulative PNL at the end
print(data.frame('Given Strategy'=tail(data_in$CUMSUM[-c(1)]),'Proposed Strategy'=tail(as.vector(eq)),row.names = as.character(tail(date_ts))))
## Optimize thresholding parameters using grid search:
# Parameters: vol.rank_MIN, rsi2_values_MAX, rsi2_values_MIN
# sig.test <-  (iif(vol.rank.test > vol.rank_MIN, 
#                   iif((rsi2_values.test < rsi2_values_MAX) , 1, -1),
#                   iif(sma.short.test < sma.long.test | ((rsi2_values.test > rsi2_values_MIN)), -1, 1)

# we will use foreach for faster for loops
# automatic install of packages if they are not installed already
list.of.packages <- c(
  "foreach",
  "doParallel",
  "ranger",
  "palmerpenguins",
  "tidyverse",
  "kableExtra"
)

new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]

if(length(new.packages) > 0){
  install.packages(new.packages, dep=TRUE)
}

#loading packages
for(package.i in list.of.packages){
  suppressPackageStartupMessages(
    library(
      package.i, 
      character.only = TRUE
    )
  )
}
# Create subset arrays to test how much these parameters change over time
# Every 5 day update of parameters
sub_ser.len <- 5
start_point <- 700
Tmax_array <- seq(start_point,length(marketclose_xts),sub_ser.len)

# Grid search for parametrs that maximize cumulative return over subset array
# and store the prediction for next day
return_max.test <- matrix(-Inf,length(Tmax_array),1)
predict_array.test <- matrix(1,length(Tmax_array),1)
max_par.test <- matrix(0,length(Tmax_array),3)
# Read from saved file for retesting
max_par.test <- read.csv("max_par.test_5day700_30_rsi2MINrange.csv")[,c("V1","V2","V3")]

print("Starting grid search. This might take a while (5-10 minutes)")
foreach (i =  1:length(Tmax_array)) %do% {
  print(paste("Updating threshold parameters for the week",i,"of",length(Tmax_array)))
  Tmax <- Tmax_array[i]
  marketclose_xts.test <- marketclose_xts[1:Tmax]
  marketclose_ret.test <- 100*diff(log(marketclose_xts.test))[-c(1)]
  #Historical volatility
  ret.log.test = ROC(marketclose_xts.test, type='continuous')
  hist.vol.test = runSD(ret.log.test, n = 21)
  vol.rank.test = percent.rank(SMA(percent.rank(hist.vol.test, 252), 21), 250)
  # Mean reversion
  rsi2_values.test = RSI(marketclose_xts.test,2)
  # Trend following
  sma.short.test <-  SMA(marketclose_xts.test, 5)
  sma.long.test <- SMA(marketclose_xts.test, 20)
  # Grid search
  foreach (vol.rank_MIN = seq(0.1,0.8,0.01)) %do% { 
    for (rsi2_values_MAX in seq(30,80,2)) {
      for (rsi2_values_MIN in 80){ #seq(30,80,10)) { # No effect of changes
        # Prediction signal calculation
        sig.test <-  (iif(vol.rank.test > vol.rank_MIN, 
                          iif((rsi2_values.test < rsi2_values_MAX) , 1, -1),
                          iif(sma.short.test < sma.long.test | ((rsi2_values.test > rsi2_values_MIN)), -1, 1)
                          
        ))
        # Save this for future use
        predict.test <- tail(sig.test,1)
        
        # Lag signal by one trading day
        sig.test <- Lag(sig.test)
        ret.test <- 1000*momentum(marketclose_xts.test)*sig.test
        
        return_total.test <- tail(cumsum(ret.test[-c(1)]),1)

        # Save if Cumulative return is more than current maximum
        if (return_total.test > return_max.test[i]) {
          
          return_max.test[i] <- return_total.test
          max_par.test[i,1] <- vol.rank_MIN
          max_par.test[i,2] <- rsi2_values_MAX
          max_par.test[i,3] <- rsi2_values_MIN
          predict_array.test[i] <- predict.test
          sig.test.max <- sig.test
          
        }
        
      }
      
    }
    
  }
}
# Save for fututre reuse
# write.csv(max_par.test,"max_par.test_5day700_30_rsi2MINrange.csv")
# Print optimized parameters for each sub time series
print(max_par.test)
optimized.pars_5days <- data.frame('vol.rank_MIN'=max_par.test[,1],'rsi2_values_MAX'=max_par.test[,2],'rsi2_values_MIN'=max_par.test[,3],row.names = date_ts[Tmax_array])
print(optimized.pars_5days)
# We will use values (0.67,40,80) for dates after and not including 2004-09-10 and before and including 2004-11-19 and so on.

optimized.pars_5days <- data.frame('vol.rank_MIN'=rep(max_par.test[1:length(Tmax_array),1],each=sub_ser.len),'rsi2_values_MAX'=rep(max_par.test[1:length(Tmax_array),2],each=sub_ser.len),'rsi2_values_MIN'=rep(max_par.test[1:length(Tmax_array),3],each=sub_ser.len))
print(dim((optimized.pars_5days)))
# pre-pend values for first 700 days
initial.pars_700days <- data.frame('vol.rank_MIN'=rep(.50,each=start_point),'rsi2_values_MAX'=rep(50,each=start_point),'rsi2_values_MIN'=rep(80,each=start_point))
optimized.pars_5days <- rbind(initial.pars_700days,optimized.pars_5days)
optimized.pars_5days <- optimized.pars_5days[1:length(marketclose_xts),]
print(head(optimized.pars_5days))
print(tail(optimized.pars_5days))
## Evaluate performance of new parameters
## Optimized trading signal
sig_opt <- Lag(iif(vol.rank > optimized.pars_5days$vol.rank_MIN, 
                   iif(rsi2_values < optimized.pars_5days$rsi2_values_MAX  , 1, -1),
                   iif(sma.short < sma.long | rsi2_values > optimized.pars_5days$rsi2_values_MIN, -1, 1)
))
##Plot of cumulative returns
ret_opt <- 1000*momentum(marketclose_xts)*sig_opt
eq_opt <- (cumsum(ret_opt[-c(1)]))
plot(coredata(eq_opt)~date_ts[-c(1)],type='l',col="green",xlab= "Year", ylab = "Cumulative PNL")
lines(coredata(eq)~date_ts[-c(1)])
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("5day update sw: Live trading + backtesting","Initail par switching backtesting","Buy Daily (Given)"),
       fill=c("green","black","red"))
#Clearly new parameters perform better
# We can also use the entirety of time series for backtesting, to see what could have been the performance if we knew the best parameter estimates in the past (or try hit and trial/other optimization to improve overall performance with single set of parameters for all time points) and use it to further improve the update approach 
sig_opt_optimal <- Lag(iif(vol.rank > 0.2, 
                   iif(rsi2_values < 36  , 1, -1),
                   iif(sma.short < sma.long | rsi2_values > 80, -1, 1)
))
ret_opt_optimal <- 1000*momentum(marketclose_xts)*sig_opt_optimal
eq_opt_optimal <- (cumsum(ret_opt_optimal[-c(1)]))
plot(coredata(eq_opt_optimal)~date_ts[-c(1)],col="blue",type='l',xlab= "Year", ylab = "Cumulative PNL")
lines(coredata(eq_opt)~date_ts[-c(1)],col="green")
lines(coredata(eq)~date_ts[-c(1)])
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("5day update sw Livetrading + Backtesting","Initial par switching: Backtesting","Buy Daily (Given)","Optimal par: Backtesting"),
       fill=c("green","black","red","blue"))
#Compare cumulative PNL at the end after improvements
print(data.frame('Given Strategy'=tail(data_in$CUMSUM[-c(1)]),'FixParSwitching'=tail(as.vector(eq)),'5dayUpdateSw'=tail(as.vector(eq_opt)),'Optimal'=tail(as.vector(eq_opt_optimal)),row.names = as.character(tail(date_ts))))
# Predictions for next day using these parameters:
# Prediction from optimal will be same as opt since the parameters are the same
# on last day
lastday_row <- length(marketclose_ret)

prediction_fixed <- iif(vol.rank[lastday_row] > 0.50, 
                                   iif(rsi2_values[lastday_row] < 50 , 1, -1),
                                   iif(sma.short[lastday_row] < sma.long[lastday_row] | rsi2_values[lastday_row] > 80 , -1, 1)
)

prediction_opt <- iif(vol.rank[lastday_row] > optimized.pars_5days$vol.rank_MIN[lastday_row], 
                      iif(rsi2_values[lastday_row] < optimized.pars_5days$rsi2_values_MAX[lastday_row]  , 1, -1),
                      iif(sma.short[lastday_row] < sma.long[lastday_row] | rsi2_values > optimized.pars_5days$rsi2_values_MIN[lastday_row], -1, 1)
)

# Fixed parameter switching fails to predict correctly but 5 day update parameter update does correctly predict -1
print(paste0("Prediction for next day:  ",prediction_opt))

This was also evident in the exploration plots for weekday specific trends on returns

Section III: Model Development and analysis: Part II

Weekday Specific Update

[Using weekday specific daily return volatility (weekly bars) for TF and MR switching]

## Exploiting week of the day patterns in returns
marketclose_weekday <- base::weekdays(date_ts,abbreviate=TRUE)

## Modeling Monday returns
marketclose_ret_Mon <- marketclose_ret[marketclose_weekday == "Mon"]
plot(x=date_ts[marketclose_weekday == "Mon"],y=as.vector(marketclose_ret_Mon),ylim=c(-1.5,1.5),
     xlab="Year",ylab = "% Change",type = 'l',col="pink",
     main="% Weekly change in Monday daily log returns")
lines(lowess(as.vector(marketclose_ret_Mon) ~ date_ts[marketclose_weekday == "Mon"] ,f=1/2),col="red",lwd=1)
lines(lowess(as.vector(marketclose_ret_Mon)  ~ date_ts[marketclose_weekday == "Mon"],f=1/4),col="blue",lwd=1)
lines(lowess(as.vector(marketclose_ret_Mon)  ~ date_ts[marketclose_weekday == "Mon"],f=1/8),col="green",lwd=1)
lines(lowess(as.vector(marketclose_ret_Mon)  ~ date_ts[marketclose_weekday == "Mon"],f=1/12),col="black",lwd=1)
grid(col=1,lwd=1)
legend(x = "bottomleft",legend=c("% log ret", "f=1/2 lowess","f=1/4", "f=1/8","f=1/2"),
       fill = c("pink","red","blue","green","black"), bty='n')
## Monday returns clearly show a visible pattern, we can create a time series for returns and use its predictability to improve the earlier forecast
## Switching strategy based on Monday returns:

## Mean reversion
rsi2_values_Mon = RSI(marketclose_ret_Mon/100,2)

## Trend following
sma.short_Mon <- SMA(marketclose_ret_Mon/100, 2)
sma.long_Mon <-  SMA(marketclose_ret_Mon/100, 5)

## Volatility in weekly bars of daily returns on Mondays
# Note that we had multiplied the changes by 100 when calculating `marketclose_ret`
library(quantmod)
ret.log_Mon = marketclose_ret_Mon/100
hist.vol_Mon = runSD(ret.log_Mon, n = 5)
vol.rank_Mon = percent.rank(SMA(percent.rank(hist.vol_Mon, 52), 5), 50)
## Switching strategy for Monday returns
sig_Mon_ret <-  (Lag(Lag(iif(vol.rank_Mon > 0.50, 
                         iif((rsi2_values_Mon < 50) , 1, -1),
                         iif(sma.short_Mon < sma.long_Mon | ((rsi2_values_Mon > 80)), -1, 1)
))))

## Cumulative PNL plots for predicting daily returns on Mondays
ret_Mon <- 1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"]*sig_Mon_ret
eq_Mon <- (cumsum(ret_Mon[-c(1,2)]))
x_var_Mon <- (date_ts[marketclose_weekday == "Mon"])[-c(1,2)]
plot(as.numeric(eq_Mon)~x_var_Mon,type='l',xlab="Year",ylab="Cumulative returns on Mondays",
     ylim=c(-10000,30000))

eq_opt_Mon <- as.numeric( cumsum( (ret_opt[marketclose_weekday == "Mon"])[-c(1,2)]) )
lines(eq_opt_Mon ~ x_var_Mon,col="green")

eg_givenStr <- as.numeric( cumsum( (1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"])[-c(1,2)]) )
lines(eg_givenStr ~ x_var_Mon,col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("Monday prediction","Combined switching","Buy every Monday (Given)"),
       fill=c("black","green","red"))
## Optimizing parameters

# Create subset arrays to test how much these parameters change over time
# Every 10 week update of parameters
sub_ser.len.Mon <- 5
start_point.Mon <- 140
Tmax_array.Mon <- seq(start_point.Mon,length(marketclose_ret_Mon),sub_ser.len.Mon)

# Grid search for parameters that maximize cumulative return over subset time series
# and store the prediction for next day
return_max.test.Mon <- matrix(-Inf,length(Tmax_array.Mon),1)
predict_array.test.Mon <- matrix(1,length(Tmax_array.Mon),1)
max_par.test.Mon <- matrix(0,length(Tmax_array.Mon),3)
# Or directly read from here
max_par.test.Mon <- read.csv("max_par.test.Mon.csv")[,c("V1","V2","V3")]
print("Starting grid search. This might take a while (5-10 minutes)")
foreach (i =  1:length(Tmax_array.Mon)) %do% {
  print(paste("Updating threshold parameters for the 5 week Monday time series",i,"of",length(Tmax_array.Mon)))
  Tmax.Mon <- Tmax_array.Mon[i]
  marketclose_ret_Mon.test <- marketclose_ret_Mon[1:Tmax.Mon]
  # Differences in returns on Mondays
  marketclose_ret_ret_Mon.test <- 100*diff((marketclose_ret_Mon.test))[-c(1)]
  
  ## Volatility in weekly returns
  ret.log.test.Mon <- marketclose_ret_Mon.test/100
  hist.vol.test.Mon <- runSD(ret.log.test.Mon, n = 5)
  vol.rank.test.Mon <- percent.rank(SMA(percent.rank(hist.vol.test.Mon, 52), 5), 50)
  
  ## Mean reversion
  rsi2_values.test.Mon = RSI(marketclose_ret_Mon.test/100,2)
  ## Trend following
  sma.short.test.Mon <-  SMA(marketclose_ret_Mon.test/100, 2)
  sma.long.test.Mon <- SMA(marketclose_ret_Mon.test/100, 5)
  
  # Grid search for Monday parameters
  foreach (vol.rank.Mon_MIN = seq(0.3,0.8,0.01)) %do% {
    foreach (rsi2_values.Mon_MAX = seq(30,80,2)) %do% {
      for (rsi2_values.Mon_MIN in 60){ #seq(30,80,5))  %do%{ # doesn't change
        sig.test.Mon <-  (iif(vol.rank.test.Mon > vol.rank.Mon_MIN, 
                          iif((rsi2_values.test.Mon < rsi2_values.Mon_MAX) , 1, -1),
                          iif(sma.short.test.Mon < sma.long.test.Mon | ((rsi2_values.test.Mon > rsi2_values.Mon_MIN)), -1, 1)
                          
        ))
        predict.test.Mon <- tail(sig.test.Mon,1)
        sig.test.Mon <- Lag(Lag(sig.test.Mon))
        ret.test.Mon <- 1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"]*sig.test.Mon
        #ret.test.Mon <- 1000*momentum(marketclose_ret_Mon.test)*sig.test.Mon
        
        return_total.test.Mon <- tail(cumsum(ret.test.Mon[-c(1,2)]),1)
        
        #Optimize cumulative return on Mondays
        if (return_total.test.Mon > return_max.test.Mon[i]) {
          
          return_max.test.Mon[i] <- return_total.test.Mon
          max_par.test.Mon[i,1] <- vol.rank.Mon_MIN
          max_par.test.Mon[i,2] <- rsi2_values.Mon_MAX
          max_par.test.Mon[i,3] <- rsi2_values.Mon_MIN
          predict_array.test.Mon[i] <- predict.test.Mon
          sig.test.Mon.max <- sig.test.Mon
          
        }
        
      }
      
    }
    
  }
}


#write.csv(max_par.test.Mon,"max_par.test.Mon.csv")
print(max_par.test.Mon)
# We use the optimal parameters for Monday update (0.44,62,60), to correct the earlier model
## Switching strategy for Monday returns
sig_opt.Mon <-  Lag(Lag(iif(vol.rank_Mon > 0.44, 
                             iif((rsi2_values_Mon < 62) , 1, -1),
                             iif(sma.short_Mon < sma.long_Mon | ((rsi2_values_Mon > 60)), -1, 1)
)))


##Plot of cumulative returns on Mondays after and before update
plot(as.numeric(eq_Mon)~x_var_Mon,type='l',xlab="Year",ylab="Cumulative returns on Mondays",
     ylim=c(-10000,30000))
lines(eq_opt_Mon ~ x_var_Mon,col="green")
lines(eg_givenStr ~ x_var_Mon,col="red")

ret_Mon_opt <- 1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"]*sig_opt.Mon
eq_Mon_opt <- as.numeric( cumsum( (ret_Mon_opt)[-c(1,2)]) )
lines(eq_Mon_opt ~ x_var_Mon,col="blue")

grid(col=1,lwd=1)
legend(x="topleft",legend=c("5 week Monday update + 5 day update","Monday prediction","5 day update","Buy every Monday (Given)"),
       fill=c("blue","black","green","red"))
# Correcting the earlier plots
sig_opt_optimal.MonCorrection <- sig_opt_optimal
sig_opt_optimal.MonCorrection[marketclose_weekday == "Mon"][-c(1,2)] <- sig_opt.Mon[-c(1,2)]

ret_opt_optimal.MonCorrection <- 1000*momentum(marketclose_xts)[-c(1,2)]*sig_opt_optimal.MonCorrection
eq_opt_optimal.MonCorrection <- as.numeric( cumsum( (ret_opt_optimal.MonCorrection) ))
#Cumulative PNL plots for complete series after monday correction
plot(c(NA,eq_opt_optimal.MonCorrection)~date_ts[-c(1)],col="orange",type='l', ylim=c(0,140000),xlab= "Year", ylab = "Cumulative PNL")
lines(coredata(eq_opt_optimal)~date_ts[-c(1)],col="blue")
lines(coredata(eq_opt)~date_ts[-c(1)],col="green")
lines(coredata(eq)~date_ts[-c(1)])
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("5day update sw","Fixed par switching","Buy Daily (Given)","Optimal","Optimal+Monday updates"),
       fill=c("green","black","red","blue","orange"))
# Similarly we can check for weekday patterns on other days and also potential monthly or quarterly patterns

## Since next day on which prediction is needed also falls on a Monday, we can also verify the prediction from Monday series only:
lastweek_row.Mon <- length(marketclose_ret_Mon)
prediction_fixed.Mon <- iif(vol.rank_Mon[lastweek_row.Mon] > 0.50, 
                            iif(rsi2_values_Mon[lastweek_row.Mon] < 50 , 1, -1),
                            iif((sma.short_Mon[lastweek_row.Mon] < sma.long_Mon[lastweek_row.Mon]) | (rsi2_values_Mon[lastweek_row.Mon] > 80) , -1, 1)
)

prediction_opt <- iif(vol.rank_Mon[lastweek_row.Mon] > 0.44, 
                      iif(rsi2_values_Mon[lastweek_row.Mon] < 62  , 1, -1),
                      iif((sma.short_Mon[lastweek_row.Mon] < sma.long_Mon[lastweek_row.Mon])  | (rsi2_values_Mon[lastweek_row.Mon] > 60), -1, 1)
)

# Again fixed parameter switching fails to predict correctly but 2 week parameter update does correctly predict -1
print(paste0("Prediction for next day (2 week parameter update):  ",prediction_opt))
Compare cumulative PNL at the end after Monday correction
print(data.frame('Given Strategy'=tail(data_in$CUMSUM[-c(1)]),'FixParSwitching'=tail(as.vector(eq)),'5dayUpdateSw'=tail(as.vector(eq_opt)),'Optimal'=tail(as.vector(eq_opt_optimal)),'Optimal&MonUpdate'=tail(as.vector(eq_opt_optimal.MonCorrection)),row.names = as.character(tail(date_ts))))

write.csv(cbind("sig_opt_optimal.MonCorrection"=as.numeric(sig_opt_optimal.MonCorrection),"sig_opt_optimal"=as.numeric(sig_opt_optimal),"sig_opt"=as.numeric(sig_opt),'sig'=as.numeric(sig)),"trading_signals.csv",quote=FALSE,row.names = date_ts)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCiMjIFNlY3Rpb24gMDogQW5zd2VycyB0byBxdWVzdGlvbnMKCiMjIyBRdWVzdGlvbiAxCiMjIyBGaW5hbCBQcmVkaWN0aW9uIE1vZGVsIChEZWNpc2lvbiBUcmVlKToKYGBge3J9CiMgUXVlc3Rpb24gMQojIEZpbmFsIFByZWRpY3Rpb24gTW9kZWwgKERlY2lzaW9uIFRyZWUpOgoKcmVxdWlyZShSQ3VybCkKc2l0ID0gZ2V0VVJMQ29udGVudCgnaHR0cHM6Ly9naXRodWIuY29tL3N5c3RlbWF0aWNpbnZlc3Rvci9TSVQvcmF3L21hc3Rlci9zaXQuZ3onLCBiaW5hcnk9VFJVRSwgZm9sbG93bG9jYXRpb24gPSBUUlVFLCBzc2wudmVyaWZ5cGVlciA9IEZBTFNFKQpjb24gPSBnemNvbihyYXdDb25uZWN0aW9uKHNpdCwgJ3JiJykpCnNvdXJjZShjb24pCmNsb3NlKGNvbikKCiMjIEdldCBkYXRhLCB0aGlzIGlzIG9yaWdpbmFsIGZpbGUgZG93bmxvYWRlZCB3aXRob3V0IGFueSBjaGFuZ2VzCnNldHdkKCJ+L0RvY3VtZW50cy9pbnRlcnZpZXdfcHJlcC9RdWVzdGlvbjEiKQpkYXRhX2luIDwtIHJlYWQuY3N2KGZpbGUgPSAnRGF0YVNldC5jc3YnKQpkYXRlX3RzIDwtIGFzLkRhdGUoZGF0YV9pbiREQVRFLCBmb3JtYXQgPSAiJW0vJWQvJVkiKQpsaWJyYXJ5KHh0cykKbWFya2V0Y2xvc2VfeHRzIDwtIHh0cyhkYXRhX2luJE1BUktFVENMT1NFLG9yZGVyLmJ5ID0gZGF0ZV90cykKbWFya2V0Y2xvc2VfcmV0IDwtIDEwMCpkaWZmKGxvZyhtYXJrZXRjbG9zZV94dHMpKQoKI0VzdGltYXRlIGhpc3RvcmljYWwgcmVsYXRpdmUgdm9sYXRpbGl0eQpsaWJyYXJ5KHF1YW50bW9kKQpyZXQubG9nIDwtIFJPQyhtYXJrZXRjbG9zZV94dHMsIHR5cGU9J2NvbnRpbnVvdXMnKQpoaXN0LnZvbCA8LSBydW5TRChyZXQubG9nLCBuID0gMjEpCnZvbC5yYW5rIDwtIHBlcmNlbnQucmFuayhTTUEocGVyY2VudC5yYW5rKGhpc3Qudm9sLCAyNTIpLCAyMSksIDI1MCkKCiMjIE1lYW4gcmV2ZXJzaW9uIGZlYXR1cmUKcnNpMl92YWx1ZXMgPC0gUlNJKG1hcmtldGNsb3NlX3h0cywyKQoKIyMgVHJlbmQgZm9sbG93aW5nIGZlYXR1cmUKc21hLnNob3J0IDwtICBTTUEobWFya2V0Y2xvc2VfeHRzLCA1KQpzbWEubG9uZyA8LSBTTUEobWFya2V0Y2xvc2VfeHRzLCAyMCkKCnNpZ19vcHRfb3B0aW1hbCA8LSBMYWcoaWlmKHZvbC5yYW5rID4gMC4yLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHJzaTJfdmFsdWVzIDwgMzYgICwgMSwgLTEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0IDwgc21hLmxvbmcgfCByc2kyX3ZhbHVlcyA+IDgwLCAtMSwgMSkKKSkKcmV0X29wdF9vcHRpbWFsIDwtIDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKSpzaWdfb3B0X29wdGltYWwKIyBWZXJpZnkgc2lnX29wdF9vcHRpbWFsPTEgZ2l2ZXMgcmV0X29wdF9vcHRpbWFsPSBtYXJrZXQgcG5sCiMgaXQgc2VlbXMgbW9tZW50dW0gZnVuY3Rpb24gZGl2aWRlcyB0aGUgZGlmZmVyZW5jZSBieSAxMAplcV9vcHRfb3B0aW1hbCA8LSAoY3Vtc3VtKHJldF9vcHRfb3B0aW1hbFstYygxKV0pKQoKbWFya2V0Y2xvc2Vfd2Vla2RheSA8LSBiYXNlOjp3ZWVrZGF5cyhkYXRlX3RzLGFiYnJldmlhdGU9VFJVRSkKIyMgTW9kZWxpbmcgTW9uZGF5IHJldHVybnMKbWFya2V0Y2xvc2VfcmV0X01vbiA8LSBtYXJrZXRjbG9zZV9yZXRbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0KIyMgTW9uZGF5IHJldHVybnMgY2xlYXJseSBzaG93IGEgdmlzaWJsZSBwYXR0ZXJuLCB3ZSBjYW4gY3JlYXRlIGEgdGltZSBzZXJpZXMgZm9yIHJldHVybnMgYW5kIHVzZSBpdHMgcHJlZGljdGFiaWxpdHkgdG8gaW1wcm92ZSB0aGUgZWFybGllciBmb3JlY2FzdAojIyBTd2l0Y2hpbmcgc3RyYXRlZ3kgYmFzZWQgb24gTW9uZGF5IHJldHVybnM6CgojIyBNZWFuIHJldmVyc2lvbgpyc2kyX3ZhbHVlc19Nb24gPSBSU0kobWFya2V0Y2xvc2VfcmV0X01vbi8xMDAsMikKCiMjIFRyZW5kIGZvbGxvd2luZwpzbWEuc2hvcnRfTW9uIDwtIFNNQShtYXJrZXRjbG9zZV9yZXRfTW9uLzEwMCwgMikKc21hLmxvbmdfTW9uIDwtICBTTUEobWFya2V0Y2xvc2VfcmV0X01vbi8xMDAsIDUpCmxpYnJhcnkocXVhbnRtb2QpCnJldC5sb2dfTW9uID0gbWFya2V0Y2xvc2VfcmV0X01vbi8xMDAKaGlzdC52b2xfTW9uID0gcnVuU0QocmV0LmxvZ19Nb24sIG4gPSA1KQp2b2wucmFua19Nb24gPSBwZXJjZW50LnJhbmsoU01BKHBlcmNlbnQucmFuayhoaXN0LnZvbF9Nb24sIDUyKSwgNSksIDUwKQoKc2lnX29wdC5Nb24gPC0gIExhZyhMYWcoaWlmKHZvbC5yYW5rX01vbiA+IDAuNDQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKChyc2kyX3ZhbHVlc19Nb24gPCA2MikgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0X01vbiA8IHNtYS5sb25nX01vbiB8ICgocnNpMl92YWx1ZXNfTW9uID4gNjApKSwgLTEsIDEpCikpKQoKc2lnX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb24gPC0gc2lnX29wdF9vcHRpbWFsCnNpZ19vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uW21hcmtldGNsb3NlX3dlZWtkYXkgPT0gIk1vbiJdWy1jKDEsMildIDwtIHNpZ19vcHQuTW9uWy1jKDEsMildCgpyZXRfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cylbLWMoMSwyKV0qc2lnX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb24KZXFfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbiA8LSBhcy5udW1lcmljKCBjdW1zdW0oIChyZXRfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbikgKSkKCmxhc3RkYXlfcm93IDwtIGxlbmd0aChtYXJrZXRjbG9zZV9yZXQpCgpwcmVkaWN0aW9uX29wdCA8LSBpaWYodm9sLnJhbmtbbGFzdGRheV9yb3ddID4gMC4yLCAKICAgICAgICAgICAgICAgICAgICAgIGlpZihyc2kyX3ZhbHVlc1tsYXN0ZGF5X3Jvd10gPCAzNiAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0W2xhc3RkYXlfcm93XSA8IHNtYS5sb25nW2xhc3RkYXlfcm93XSB8IHJzaTJfdmFsdWVzID4gODAsIC0xLCAxKQopCgojIFByZWRpY3RzIC0xCnByaW50KHBhc3RlMCgiUHJlZGljdGlvbiBmb3IgbmV4dCBkYXk6ICAiLHByZWRpY3Rpb25fb3B0KSkKYGBgCgojIFF1ZXN0aW9uIDIKIyBNb2RlbCBjdW11bGF0aXZlIFBOTCB2cyBtYXJrZXQgUE5MCgpgYGB7cn0KCgpwbG90KGMoTkEsZXFfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbil+ZGF0ZV90c1stYygxKV0sY29sPSJyZWQiLHR5cGU9J2wnLCB5bGltPWMoMCwxNDAwMDApLHhsYWI9ICJZZWFyIiwgeWxhYiA9ICJDdW11bGF0aXZlIFBOTCIpCmxpbmVzKGNvcmVkYXRhKGVxX29wdF9vcHRpbWFsKSB+IGRhdGVfdHNbLWMoMSldLGNvbD0iYmx1ZSIpCmxpbmVzKGRhdGFfaW4kQ1VNU1VNWy1jKDEpXX5kYXRlX3RzWy1jKDEpXSxjb2w9ImJsYWNrIikKZ3JpZChjb2w9MSxsd2Q9MSkKbGVnZW5kKHg9InRvcGxlZnQiLGxlZ2VuZD1jKCJNb2RlbCBDdW11bGF0aXZlIFBOTCAoTW9uZGF5IHRyZW5kIGNvcnJlY3Rpb24pIEJhY2t0ZXN0aW5nIiwiTW9kZWwgQ3VtdWxhdGl2ZSBQTkw6IEJhY2t0ZXN0aW5nIiwiTWFya2V0IEN1bXVsYXRpdmUgUE5MIiksCiAgICAgICBmaWxsPWMoInJlZCIsImJsdWUiLCJibGFjayIpKQpgYGAKCgoKCiMgTW9kZWwgRGV2ZWxvcG1lbnQKCiMjICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNlY3Rpb24gSTogRGF0YSBDbGVhbmluZyAoUHl0aG9uKQpgYGB7cHl0aG9ufQojIFRoaXMgc25pcHBldCBpcyB3cml0dGVuIGluIHB5dGhvbjMKIyBpbXBvcnQgcGFuZGFzIGFzIHBkCiMgaW1wb3J0IG51bXB5IGFzIG5wCiMgZGYgPSBwZC5yZWFkX2NzdignRGF0YVNldC5jc3YnKQojIAojIGZvciBjb2x1bW4gaW4gZGYuY29sdW1uczoKIyAgIHByaW50KGNvbHVtbikKIyAgIHByaW50KGRmW2RmW2NvbHVtbl0uaXNudWxsKCldLmluZGV4LnRvbGlzdCgpKQojIAojIGRmWydGRUFUVVJFXzMnXSA9IGRmWydGRUFUVVJFXzMnXS5zdHIucmVwbGFjZSgnJScsIiIpCiMgZGZbJ0RBVEUnXSA9IHBkLnRvX2RhdGV0aW1lKGRmWydEQVRFJ10sIGZvcm1hdD0iJW0vJWQvJVkiKQojCiMgIyMjIyBXZSBjYW4gdXNlIGEgZGljdGlvbmFyeSB0byBtYXAgdGhlIHN0cmluZyBwYXR0ZXJuIHRvIG5hbgojICMjIyMgY2xlYW5pbmdfZGljdCA9IHsnXiMuKic6IG5wLm5hbn0KIyAjIyMjIGRmWydGRUFUVVJFXzMnXSA9IGRmWydGRUFUVVJFXzMnXS5yZXBsYWNlKGNsZWFuaW5nX2RpY3QsIHJlZ2V4PVRydWUpCiMgIyMjIyBkZlsnQklOQVJZX0ZFQVRVUkUnXSA9IGRmWydCSU5BUllfRkVBVFVSRSddLnJlcGxhY2UoY2xlYW5pbmdfZGljdCwgcmVnZXg9VHJ1ZSkKIyAjIE9SIFVzaW5nIGVycm9ycz0nY29lcmNlJyB3aWxsIHJlcGxhY2UgYWxsIG5vbi1udW1lcmljIHZhbHVlcyB3aXRoIE5hTgojIGRmWydCSU5BUllfRkVBVFVSRSddID0gcGQudG9fbnVtZXJpYyhkZlsnQklOQVJZX0ZFQVRVUkUnXSxlcnJvcnM9J2NvZXJjZScpCiMgZGZbJ0ZFQVRVUkVfMyddID0gcGQudG9fbnVtZXJpYyhkZlsnRkVBVFVSRV8zJ10sZXJyb3JzPSdjb2VyY2UnKQojICMjIyMgRHJvcCBhbGwgbmFuCiMgZGZfcm1uYSA9IGRmLmRyb3BuYSgpCiMgIyMjIyBTYXZlIGZpbGUKIyBkZl9ybW5hLnRvX2NzdignRGF0YVNldF9ybU5BLmNzdicpCmBgYAoKIyBTZWN0aW9uIElJOiBEYXRhIEV4cGxvcmF0aW9uIChSKQoKCmBgYHtyfQpzZXR3ZCgifi9Eb2N1bWVudHMvaW50ZXJ2aWV3X3ByZXAvUXVlc3Rpb24xIikKZGF0YV9pbiA8LSByZWFkLmNzdihmaWxlID0gJ0RhdGFTZXQuY3N2JykKZGF0ZV90cyA8LSBhcy5EYXRlKGRhdGFfaW4kREFURSwgZm9ybWF0ID0gIiVtLyVkLyVZIikKbWFya2V0Y2xvc2VfdHMgPC0gdHMoZGF0YV9pbiRNQVJLRVRDTE9TRSwgc3RhcnQ9ZGF0ZV90c1sxXSkKI3ByaW50KG1hcmtldGNsb3NlX3RzKQpwbG90LnRzKG1hcmtldGNsb3NlX3RzKQpgYGAKYGBge3J9CmxpYnJhcnkoeHRzKQptYXJrZXRjbG9zZV94dHMgPC0geHRzKHg9ZGF0YV9pbiRNQVJLRVRDTE9TRSxvcmRlci5ieSA9IGRhdGVfdHMpCmF1dG9wbG90KG1hcmtldGNsb3NlX3h0cykKYGBgCgpgYGB7cn0KIyBTcGxpdCBtYXJrZXRjbG9zZSBieSB3ZWVrIGFuZCBjYWxjdWxhdGUgYXZlcmFnZXMgcGVyIHdlZWsKd2Vla2x5X21hcmtldGNsb3NlX3h0cyA8LSBzcGxpdChtYXJrZXRjbG9zZV94dHMsIGYgPSAid2Vla3MiKQpwcmludChoZWFkKHdlZWtseV9tYXJrZXRjbG9zZV94dHNbWzFdXSkpCnByaW50KGhlYWQod2Vla2x5X21hcmtldGNsb3NlX3h0c1tbMl1dKSkKCmRhdGVzX3dlZWtseV9lbmRwb2ludHMgPC0geHRzOjplbmRwb2ludHMobWFya2V0Y2xvc2VfeHRzLG9uPSd3ZWVrcycpCnJlYWRpbmdzX3Blcl93ZWVrIDwtIGRhdGVzX3dlZWtseV9lbmRwb2ludHNbLWMoMSldLWRhdGVzX3dlZWtseV9lbmRwb2ludHNbLWMobGVuZ3RoKGRhdGVzX3dlZWtseV9lbmRwb2ludHMpKV0KCiMgcHJpbnQgd2Vla3Mgd2l0aCBsZXNzIHRoYW4gNSB2YWx1ZXMgcGVyIHdlZWsKZm9yICh3ZWVrSUQgaW4gMTpsZW5ndGgod2Vla2x5X21hcmtldGNsb3NlX3h0cykpIHsKICBpZiAobGVuZ3RoKHdlZWtseV9tYXJrZXRjbG9zZV94dHNbW3dlZWtJRF1dWywxXSkgPCA1KSB7CiAgICB3ZWVrX2xlbiA8LSBsZW5ndGgod2Vla2x5X21hcmtldGNsb3NlX3h0c1tbd2Vla0lEXV0pCiAgICB3ZWVrX2VuZHBvaW50X0lEIDwtIGRhdGVzX3dlZWtseV9lbmRwb2ludHNbMSt3ZWVrSURdCiAgICBwcmludChwYXN0ZSgiV2Vla0lEOiIsd2Vla0lELCIsIG51bV9yZWFkaW5nczoiLHdlZWtfbGVuKSkKICAgIHByaW50KHBhc3RlKCJXZWVrX3N0YXJ0OiIsYXMuY2hhcmFjdGVyKGRhdGVfdHNbd2Vla19lbmRwb2ludF9JRC13ZWVrX2xlbisxXSksCiAgICAiLCBXZWVrX2VuZDoiLGFzLmNoYXJhY3RlcihkYXRlX3RzW3dlZWtfZW5kcG9pbnRfSURdKSApICkKICB9Cn0KCmBgYAoKYGBge3J9CndlZWtseV9hdmdfbWFya2V0Y2xvc2UgPC0gbGFwcGx5KFggPSB3ZWVrbHlfbWFya2V0Y2xvc2VfeHRzLCBmdW5jdGlvbihYKXtyZXR1cm4obWVhbihYKSl9KQphbGxfd2Vla3MgPC0gdHModW5saXN0KHdlZWtseV9hdmdfbWFya2V0Y2xvc2UpKQpmaWx0ZXJlZF93ZWVrcyA8LSBhbGxfd2Vla3MKZmlsdGVyZWRfd2Vla3NbcmVhZGluZ3NfcGVyX3dlZWs8NV0gPC0gTmFOCiNmaWx0ZXJlZF93ZWVrcyA8LSB0cyh1bmxpc3Qod2Vla2x5X2F2Z19tYXJrZXRjbG9zZSlbcmVhZGluZ3NfcGVyX3dlZWs9PTVdKQpjbG9zZXMgPC0gY2JpbmQoYWxsX3dlZWtzLCBmaWx0ZXJlZF93ZWVrcykKYXV0b3Bsb3QoY2xvc2VzKQojYXV0b3Bsb3QodHModW5saXN0KHdlZWtseV9hdmdfbWFya2V0Y2xvc2UpKSkKYGBgCgoKSXQgd291bGQgYmUgaW50ZXJlc3RpbmcgdG8gc2VlIGVmZmVjdHMgb2YgaG9saWRheXMvbG9uZyB3ZWVrZW5kcywgYW5udWFsIGV2ZW50cyAoZWcgc3VwZXJib3dsLCBxdWFkIHdpdGNoaW5nKS4gV2Ugd2lsbCBpZ25vcmUgdGhlbSBmb3Igbm93LiBPdGhlciBmYWN0b3JzIGluY2x1ZGUgc2VjdG9yIHNwZWNpZmljIHNlbnRpbWVudCBvciB3aG9sZSBtYXJrZXQgc2VudGltZW50LCB3aGljaCBob3BlZnVsbHkgZGVjb21wb3NpdGlvbiBiZWxvdyB3aWxsIHVuY292ZXIuIAoKTnVtYmVyIG9mIGRhaWx5IHJlYWRpbmdzIGJ5IHllYXI6CgpgYGB7cn0KCnRhYmxlKGZvcm1hdChkYXRlX3RzLCIlWSIpKQpgYGAKCk51bWJlciBvZiB3ZWVrbHkgcmVhZGluZ3MgYnkgeWVhcjoKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90c1tkYXRlc193ZWVrbHlfZW5kcG9pbnRzWy1jKDEpXV0sIiVZIikpCmBgYAoKTnVtYmVyIG9mIHRyYWRpbmcgZGF5cyBwZXIgdHJhZGluZyB3ZWVrIChhdmcpIGJ5IHllYXIKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90cywiJVkiKSkvdGFibGUoZm9ybWF0KGRhdGVfdHNbZGF0ZXNfd2Vla2x5X2VuZHBvaW50c1stYygxKV1dLCIlWSIpKQpgYGAKCk51bWJlciBvZiB0cmFkaW5nIGRheXMgcGVyIG1vbnRoIGJ5IHllYXIKCmBgYHtyfQpkYXRlc19tb250aGx5X2VuZHBvaW50cyA8LSB4dHM6OmVuZHBvaW50cyhtYXJrZXRjbG9zZV94dHMsb249J21vbnRocycpCnRhYmxlKGZvcm1hdChkYXRlX3RzW2RhdGVzX21vbnRobHlfZW5kcG9pbnRzWy1jKDEpXV0sIiVZIikpCnRhYmxlKGZvcm1hdChkYXRlX3RzLCIlWSIpKS90YWJsZShmb3JtYXQoZGF0ZV90c1tkYXRlc19tb250aGx5X2VuZHBvaW50c1stYygxKV1dLCIlWSIpKQpgYGAKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90cywiJW0vJVkiLHN0YXJ0PSgiMDcvMjAwMiIpKSkKYGBgCgpgYGB7cn0KbWVhbih0YWJsZShmb3JtYXQoZGF0ZV90cywiJW0vJVkiKSkpCmBgYAoKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90cywiJVkiKSkKYGBgCgoKTGFzdCByZWFkaW5nIGRhdGUgZm9yIGVhY2ggeWVhcjoKCmBgYHtyfQpkYXRlX3RzW3h0czo6ZW5kcG9pbnRzKG1hcmtldGNsb3NlX3h0cyxvbj0neWVhcnMnKVstYygxKV1dCmBgYAoKWWVhciAyMDAzLDA0LDA1LDA2IHNlYXNvbmFsIHRyZW5kczoKYGBge3J9CndlZWtseV9hdmdfbWFya2V0Y2xvc2VfMjAwMzQ1NiAgPC0gdHMoYXMubnVtZXJpYyh1bmxpc3Qod2Vla2x5X2F2Z19tYXJrZXRjbG9zZSkpWygyNisxKTooMjYrNTMrNTIrNTIrNTIpXSxmcmVxdWVuY3k9NTIpCiNsaWJyYXJ5KGZvcmVjYXN0KQpub190cmFuc2Zvcm1fMjAwMzQ1NiA8LSBzdGwobG9nKHdlZWtseV9hdmdfbWFya2V0Y2xvc2VfMjAwMzQ1NiksIHMud2luZG93PTUzKQojbm9fdHJhbnNmb3JtXzIwMDM0NTYgPC0gbXN0bCh3ZWVrbHlfYXZnX21hcmtldGNsb3NlXzIwMDM0NTYpCmF1dG9wbG90KG5vX3RyYW5zZm9ybV8yMDAzNDU2KQojVGltZVNlcmllc1dlZWtseURlY29tcG9zZWQ8LXN0bCh0c19kYXRhICwgcy53aW5kb3c9InBlcmlvZGljIikKYGBgCgpTZWFzb25hbCB0cmVuZHM6CgpgYGB7cn0KbGlicmFyeShmb3JlY2FzdCkKbm9fdHJhbnNmb3JtXzIwMDM0NTYgJT4lIHNlYXNvbmFsKCkgJT4lIGdnc3Vic2VyaWVzcGxvdCgpCmBgYAoKYGBge3J9Cm5vX3RyYW5zZm9ybV8yMDAzNDU2ICU+JSByZW1haW5kZXIoKSAlPiUgYXV0b3Bsb3QoKQpgYGAKCgpgYGB7cn0Kcm0obGlzdD1scygpKQpgYGAKCmBgYHtyfQpzZXR3ZCgifi9Eb2N1bWVudHMvaW50ZXJ2aWV3X3ByZXAvUXVlc3Rpb24xIikKZGF0YV9pbiA8LSByZWFkLmNzdihmaWxlID0gJ0RhdGFTZXQuY3N2JykKZGF0ZV90cyA8LSBhcy5EYXRlKGRhdGFfaW4kREFURSwgZm9ybWF0ID0gIiVtLyVkLyVZIikKbWFya2V0Y2xvc2VfdHMgPC0gdHMoZGF0YV9pbiRNQVJLRVRDTE9TRSwgc3RhcnQ9ZGF0ZV90c1sxXSxmcmVxdWVuY3kgPSAyNTEuNjI1KQpwbG90LnRzKG1hcmtldGNsb3NlX3RzKQpgYGAKCgoKR2l2ZW4gdGhlIGlycmVndWxhciBuYXR1cmUgb2YgdGhlIHRpbWUgc2VyaWVzIHdlIHVzZSB4dHMgbW9kZWxpbmcgYW5kIGVzdGltYXRlIGRhaWx5IHJldHVybnMgJQoKYGBge3J9CgpsaWJyYXJ5KHh0cykKbWFya2V0Y2xvc2VfeHRzIDwtIHh0cyh4PWRhdGFfaW4kTUFSS0VUQ0xPU0Usb3JkZXIuYnkgPSBkYXRlX3RzKQptYXJrZXRjbG9zZV9yZXQgPC0gMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpKQojbWFya2V0Y2xvc2VfcmV0IDwtIGFzLnpvbyhtYXJrZXRjbG9zZV9yZXRbLWMoMSldKQpwbG90LnpvbyhtYXJrZXRjbG9zZV9yZXQsIHlsYWIgPSAiRGFpbHkgcmV0dXJucyAoJSkiLCBtYWluID0gIlBlcmNlbnRhZ2UgZGFpbHkgcmV0dXJucyIpCiMgbG93ZXNzIGZpdCB3aXRoIHRoZSBmID0gMS9hdmVyYWdlICMgdHJhZGluZyBkYXlzIGluIGEgbW9udGgKIyBDYWxjdWxhdGlvbiBmb3IgMjAuOCBmb2xsb3cgYmVsb3cKbGluZXMoaW5kZXgobWFya2V0Y2xvc2VfcmV0KSwgbG93ZXNzKG1hcmtldGNsb3NlX3JldFssMV0sIGYgPSAxLzIwLjgpJHksIGNvbCA9ICJyZWQiLCBsd2QgPSAyKQpgYGAKCmBgYHtyfQojIFZvbGF0aWxpdHkgYXMgZnVuY3Rpb24gb2YgbW9udGgKcGxvdChqaXR0ZXIoYXMubnVtZXJpYyhmb3JtYXQoaW5kZXgobWFya2V0Y2xvc2VfcmV0KSwiJW0iKSksIGFtb3VudCA9IDEvMyksIG1hcmtldGNsb3NlX3JldCwgcGNoID0gMjAsIHlsYWIgPSAiRGFpbHkgcGVyY2VudCByZXR1cm5zIiwgeGxhYiA9ICJNb250aCIsIGJ0eSA9ICJsIikKYGBgCgpgYGB7cn0KYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3RvcihxdWFydGVycyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsYWJlbHM9YygiUTMiLCJRNCIsIlExIiwiUTIiKSksIHhsYWIgPSAiUXVhcnRlciIsIHBjaCA9IDIwLCBjb2wgPSByZ2IoMCwgMCwgMCwgMC40KSwgeWxhYiA9ICJEYWlseSBwZXJjZW50IHJldHVybnMiLCBidHkgPSAibCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgpgYGB7cn0KYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcihtb250aHMoaW5kZXgobWFya2V0Y2xvc2VfcmV0KSxhYmJyZXZpYXRlID0gVFJVRSksICAgICBsZXZlbHMgPSBjKCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiLCJKYW4iLCJGZWIiLCJNYXIiLCJBcHIiLCJNYXkiLCJKdW4iKSksIHhsYWIgPSAiTW9udGgiLCBwY2ggPSAyMCwgY29sID0gcmdiKDAsIDAsIDAsIDAuNCksIHlsYWIgPSAiRGFpbHkgcGVyY2VudCByZXR1cm5zIiwgYnR5ID0gImwiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKCmBgYHtyfQpib3hwbG90KGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXQpIH4gZmFjdG9yKHdlZWtkYXlzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpLAogICAgICAgIGxldmVscyA9IGMoIk1vbiIsIlR1ZSIsIldlZCIsIlRodSIsIkZyaSIpKSAseGxhYiA9ICJEYXkgb2YgdGhlIHdlZWsiLCBwY2ggPSAyMCwgY29sID0gcmdiKDAsIDAsIDAsIDAuNCksIHlsYWIgPSAiRGFpbHkgcGVyY2VudCByZXR1cm5zIiwgYnR5ID0gImwiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKCmBgYHtyfQpteXBsb3QgPC0gYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSkqZmFjdG9yKG1vbnRocyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiLCJKYW4iLCJGZWIiLCJNYXIiLCJBcHIiLCJNYXkiLCJKdW4iKSksCnhsYWIgPSAiRGF5IG9mIHRoZSB3ZWVrIGluIGdpdmVuIG1vbnRoIiwgcGNoID0gMjAsIGNvbCA9IHJnYigwLCAwLCAwLCAwLjQpLCB5bGFiID0gIkRhaWx5IHBlcmNlbnQgcmV0dXJucyIsIGJ0eSA9ICJsIikKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpteXBsb3QgPC0gYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSkqZmFjdG9yKHF1YXJ0ZXJzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpLGxldmVscyA9IGMoIlEzIiwiUTQiLCJRMSIsIlEyIikpLAp4bGFiID0gIkRheSBvZiB0aGUgd2VlayBpbiBnaXZlbiB5ZWFyIiwgcGNoID0gMjAsIGNvbCA9IHJnYigwLCAwLCAwLCAwLjQpLCB5bGFiID0gIkRhaWx5IHBlcmNlbnQgcmV0dXJucyIsIGJ0eSA9ICJsIikKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpteXBsb3QgPC0gYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSkqZmFjdG9yKGZvcm1hdChpbmRleChtYXJrZXRjbG9zZV9yZXQpLCIlWSIpLGxldmVscyA9IGMoIjIwMDIiLCIyMDAzIiwiMjAwNCIsIjIwMDUiLCIyMDA2IiwiMjAwNyIpKSwKeGxhYiA9ICJEYXkgb2YgdGhlIHdlZWsgaW4gZ2l2ZW4geWVhciIsIHBjaCA9IDIwLCBjb2wgPSByZ2IoMCwgMCwgMCwgMC40KSwgeWxhYiA9ICJEYWlseSBwZXJjZW50IHJldHVybnMiLCBidHkgPSAibCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgpNb25kYXkgcmV0dXJucyBhcyBmdW5jdGlvbiBvZiByZXR1cm4gb24gZnJpZGF5IHJldHVybiBpbiBwcmV2IHdlZWsgYnkgeWVhcgoKYGBge3J9CiNoZWFkKHdoaWNoKGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSk9PSJNb24iKSkKI2hlYWQod2hpY2goZmFjdG9yKHdlZWtkYXlzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpLGxldmVscyA9IGMoIk1vbiIsIlR1ZSIsIldlZCIsIlRodSIsIkZyaSIpKT09IkZyaSIpKQoKCm1vbmRheV9pbmRpY2VzIDwtIHdoaWNoKHdlZWtkYXlzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpPT0iTW9uIikKbW9uZGF5X2luZGljZXMgPC0gbW9uZGF5X2luZGljZXNbLWMoMSldCnBsb3QoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFttb25kYXlfaW5kaWNlc10pfmFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzLTEpXSksCiAgICAgeGxhYj0iUHJldiBkYXkgY2hhbmdlIix5bGFiPSJNb25kYXkgY2hhbmdlIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbbW9uZGF5X2luZGljZXNdKX5hcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0xKV0pLGY9MS8yKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgoKYGBge3J9CgpwbG90LnRzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzKV0pLHlsaW09YygtMS41LDEuNSksCiAgICAgeGxhYj0iRGF0ZSIseWxhYiA9ICIlIENoYW5nZSIpCiNsaW5lcyhhcy52ZWN0b3IoMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpWyhtb25kYXlfaW5kaWNlcy0xKV0pKSxjb2w9MikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzLTEpXSksZj0xLzIpLGNvbD0iZ3JlZW4iLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMpXSksZj0xLzIpLGNvbD0icmVkIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0yKV0pLGY9MS8yKSxjb2w9ImJsdWUiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKI2xpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0zKV0pLGY9MS8yKSxjb2w9InBpbmsiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzKV0pKSxmPTEvMiksY29sPSJibGFjayIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMSldKSksZj0xLzIpLGNvbD0ib3JhbmdlIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMildKSksZj0xLzIpLGNvbD0ieWVsbG93Iix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhzaWduKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0KVttb25kYXlfaW5kaWNlc10pKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYApgYGB7cn0KcGxvdC50cyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcyldKSx5bGltPWMoLTEuNSwxLjUpLAogICAgIHhsYWI9IkRhdGUiLHlsYWIgPSAiJSBDaGFuZ2UiKQojbGluZXMoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMSldKSksY29sPTIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0xKV0pLGY9MS80KSxjb2w9ImdyZWVuIix0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzKV0pLGY9MS80KSxjb2w9InJlZCIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMtMildKSxmPTEvNCksY29sPSJibHVlIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMtMyldKSxmPTEvMiksY29sPSJwaW5rIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IoMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpWyhtb25kYXlfaW5kaWNlcyldKSksZj0xLzQpLGNvbD0iYmxhY2siLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzLTEpXSkpLGY9MS80KSxjb2w9Im9yYW5nZSIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQojbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzLTIpXSkpLGY9MS8yKSxjb2w9InllbGxvdyIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQojbGluZXMoc2lnbihhcy5udW1lcmljKG1hcmtldGNsb3NlX3JldClbbW9uZGF5X2luZGljZXNdKSkKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpwbG90LnRzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzKV0pLHlsaW09YygtMS41LDEuNSksCiAgICAgeGxhYj0iRGF0ZSIseWxhYiA9ICIlIENoYW5nZSIpCiNsaW5lcyhhcy52ZWN0b3IoMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpWyhtb25kYXlfaW5kaWNlcy0xKV0pKSxjb2w9MikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzLTEpXSksZj0xLzgpLGNvbD0iZ3JlZW4iLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMpXSksZj0xLzgpLGNvbD0icmVkIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0yKV0pLGY9MS84KSxjb2w9ImJsdWUiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKI2xpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0zKV0pLGY9MS8yKSxjb2w9InBpbmsiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzKV0pKSxmPTEvOCksY29sPSJibGFjayIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMSldKSksZj0xLzgpLGNvbD0ib3JhbmdlIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMildKSksZj0xLzIpLGNvbD0ieWVsbG93Iix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhzaWduKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0KVttb25kYXlfaW5kaWNlc10pKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKCkV2ZW4gd2l0aCBmPTEvOCBpdCBzZWVtcyBNb25kYXkgZGFpbHkgcmV0dXJucyB3aWxsIGZvbGxvdyBzdHJpY3RseSBkb3dud2FyZCB0cmVuZAoKYGBge3J9CiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTEpLDExNzMpXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTIpLDExNzIpXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzKSldKSxmPTEvOCkkeSwxNSkKCnBsb3QodGFpbChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0xKSwxMTczKV0pLGY9MS84KSR5LDUwKSx5bGltPWMoLTEsMSksdHlwZT0nbCcsbWFpbj0idHJlbmQgNTAgZGF5IHJldHVybnMgYnkgZGF5IG9mIHRoZSB3ZWVrIix5bGFiPSIlIENoYW5nZSIseGxhYj0iZGF5IikKbGluZXModGFpbChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0yKSwxMTcyKV0pLGY9MS84KSR5LDUwKSxjb2w9ImJsdWUiKQpsaW5lcyh0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksZj0xLzgpJHksNTApLGNvbD0iZ3JlZW4iKQpsaW5lcyh0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksZj0xLzgpJHksNTApLGNvbD0ib3JhbmdlIikKbGluZXModGFpbChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcykpXSksZj0xLzgpJHksNTApLGNvbD0icmVkIikKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpwbG90KGMocmVwKDAsNTApLHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXQpLDUwKSkseWxpbT1jKC0xLDEpLHR5cGU9J2wnLG1haW49IjUwICYgMjAwIGRheSBNQSBvZiBkYWlseSByZXR1cm5zIix5bGFiPSIlIENoYW5nZSIseGxhYj0iZGF5Iixjb2w9InJlZCIpCmxpbmVzKGMocmVwKDAsMjAwKSxyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSwyMDApKSxjb2w9ImdyZWVuIikKI2xpbmVzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXQpKQpsaW5lcyhsb3dlc3MobWFya2V0Y2xvc2VfcmV0WywxXSwgZiA9IDEvMjAuOCkkeSwgbHdkID0gMikKI2xpbmVzKHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbYygobW9uZGF5X2luZGljZXMtMiksMTE3MildKSw1MCksY29sPSJibHVlIikKI2xpbmVzKHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbYygobW9uZGF5X2luZGljZXMtMyksMTE3MSldKSw1MCksY29sPSJncmVlbiIpCiNsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksNTApLGNvbD0ib3JhbmdlIikKI2xpbmVzKHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbbW9uZGF5X2luZGljZXNdKSw1MCksY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKYGBge3J9CnBsb3Qocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0xKSwxMTczKV0pLDUwKSx5bGltPWMoLS4yNSwuMjUpLHR5cGU9J2wnLG1haW49IjUwIHdlZWsgTUEgb2Ygd2Vla2x5IHJldHVybnMgYnkgZGF5IG9mIHRoZSB3ZWVrIix5bGFiPSIlIENoYW5nZSIseGxhYj0iZGF5IikKbGluZXMocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0yKSwxMTcyKV0pLDUwKSxjb2w9ImJsdWUiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksNTApLGNvbD0iZ3JlZW4iKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksNTApLGNvbD0ib3JhbmdlIikKbGluZXMocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFttb25kYXlfaW5kaWNlc10pLDUwKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCmBgYHtyfQpwbG90KHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbYygobW9uZGF5X2luZGljZXMtMSksMTE3MyldKSwyMDApLHlsaW09YygtLjI1LC4yNSksdHlwZT0nbCcsbWFpbj0iMjAwIHdlZWsgTUEgcmV0dXJucyBieSBkYXkgb2YgdGhlIHdlZWsiLHlsYWI9IiUgQ2hhbmdlIix4bGFiPSJkYXkiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTIpLDExNzIpXSksMjAwKSxjb2w9ImJsdWUiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksMjAwKSxjb2w9ImdyZWVuIikKbGluZXMocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy00KSwxMTcwKV0pLDIwMCksY29sPSJvcmFuZ2UiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W21vbmRheV9pbmRpY2VzXSksMjAwKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgptb25kYXkgcmV0dXJuIHdydCBwcmV2IG1vbmRheQoKYGBge3J9CmluZGljZXNfMjAwNyA8LSB3aGljaChhcy5udW1lcmljKGZvcm1hdChpbmRleChtYXJrZXRjbG9zZV9yZXQpLCIlWSIpKSAlaW4lIGMoMjAwNiwyMDA3KSkKbW9uZGF5X2luZGljZXNfMjAwNyA8LSBtb25kYXlfaW5kaWNlc1ttb25kYXlfaW5kaWNlcyAlaW4lIGluZGljZXNfMjAwN10KcGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W21vbmRheV9pbmRpY2VzXzIwMDddKX5hcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlc18yMDA3LTEpXSksCiAgICAgeGxhYj0iUHJldiBkYXkgY2hhbmdlIDIwMDYtMjAwNyIseWxhYj0iTW9uZGF5IGNoYW5nZSAyMDA2LTIwMDciKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFttb25kYXlfaW5kaWNlc18yMDA3XSl+YXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXNfMjAwNy0xKV0pLGY9MS8yKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgoKYGBge3J9CiNwbG90Lnpvbyh4dHMoc2lnbihhcy5udW1lcmljKG1hcmtldGNsb3NlX3JldClbbW9uZGF5X2luZGljZXNfMjAwN10pKnNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpWyhtb25kYXlfaW5kaWNlc18yMDA3LTEpXSksb3JkZXIuYnkgPSBpbmRleChtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSxtYWluPSAiU2lnbiBvZiBNb25kYXkgY2hhbmdlIHRpbWVzIHNpZ24gb2YgcHJldiBkYXkiKQojbGluZXMoeHRzKHNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSxvcmRlci5ieSA9IGluZGV4KG1hcmtldGNsb3NlX3JldClbbW9uZGF5X2luZGljZXNfMjAwN10pLGNvbD0icmVkIikKI2xpbmVzKHNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSkKcGxvdChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXNfMjAwNy0xKV0pLGY9MS8yKSxjb2w9ImdyZWVuIix5bGltPWMoLTEsMSksCiAgICAgeGxhYj0iRGF0ZSIseWxhYiA9ICIlIENoYW5nZSIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlc18yMDA3KV0pLGY9MS8yKSxjb2w9InJlZCIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXNfMjAwNy0yKV0pLGY9MS8yKSxjb2w9ImJsdWUiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzXzIwMDctMyldKSxmPTEvMiksY29sPSJwaW5rIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKHNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSkKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCiMjIFBsb3Qgb2YgZmVhdHVyZXMKCgoKYGBge3J9CnNldHdkKCJ+L0RvY3VtZW50cy9pbnRlcnZpZXdfcHJlcC9RdWVzdGlvbjEiKQpkYXRhX2luX2NsZWFuZWQgPC0gcmVhZC5jc3YoZmlsZSA9ICdEYXRhU2V0X3JtTkEuY3N2JykKZGF0ZV90c19jbGVhbmVkIDwtIGFzLkRhdGUoZGF0YV9pbl9jbGVhbmVkJERBVEUsIGZvcm1hdCA9ICIlWS0lbS0lZCIpCm1hcmtldGNsb3NlX3RzX2NsZWFuZWQgPC0gdHMoZGF0YV9pbl9jbGVhbmVkJE1BUktFVENMT1NFLCBzdGFydD1kYXRlX3RzX2NsZWFuZWRbMV0sZnJlcXVlbmN5ID0gMjUxLjYyNSkKcGxvdC50cyhtYXJrZXRjbG9zZV90c19jbGVhbmVkKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHh0cykKbWFya2V0Y2xvc2VfeHRzX2NsZWFuZWQgPC0geHRzKHg9ZGF0YV9pbl9jbGVhbmVkJE1BUktFVENMT1NFLG9yZGVyLmJ5ID0gZGF0ZV90c19jbGVhbmVkKQptYXJrZXRjbG9zZV9yZXRfY2xlYW5lZCA8LSAxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0c19jbGVhbmVkKSkKIyMgbWFya2V0Y2xvc2VfcmV0X2NsZWFuZWQgPC0gbWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRbLWMoMSldCiNtYXJrZXRjbG9zZV9yZXQgPC0gYXMuem9vKG1hcmtldGNsb3NlX3JldFstYygxKV0pCnBsb3Quem9vKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkLCB5bGFiID0gIkRhaWx5IHJldHVybnMgKCUpIiwgbWFpbiA9ICJQZXJjZW50YWdlIGRhaWx5IHJldHVybnMiKQojIGxvd2VzcyBmaXQgd2l0aCB0aGUgZiA9IDEvYXZlcmFnZSAjIHRyYWRpbmcgZGF5cyBpbiBhIG1vbnRoCiMgQ2FsY3VsYXRpb24gZm9yIDIwLjggZm9sbG93IGJlbG93CmxpbmVzKGluZGV4KG1hcmtldGNsb3NlX3JldF9jbGVhbmVkKSwgbG93ZXNzKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkWywxXSwgZiA9IDEvMjAuOCkkeSwgY29sID0gInJlZCIsIGx3ZCA9IDIpCmZlYXR1cmUxX3h0cyA8LSB4dHMoeD1kYXRhX2luX2NsZWFuZWQkRkVBVFVSRV8xLG9yZGVyLmJ5ID0gZGF0ZV90c19jbGVhbmVkKQpmZWF0dXJlMV9kaWZmX3h0cyA8LSBkaWZmLnh0cygoZmVhdHVyZTFfeHRzKSkKIyMgZmVhdHVyZTFfZGlmZl94dHMgPC0gZmVhdHVyZTFfZGlmZl94dHNbLWMoMSldCmxpbmVzKGluZGV4KGZlYXR1cmUxX2RpZmZfeHRzKSwgbG93ZXNzKGZlYXR1cmUxX2RpZmZfeHRzWywxXSwgZiA9IDEvMjAuOCkkeSwgY29sID0gImJsdWUiLCBsd2QgPSAyKQpmZWF0dXJlMl94dHMgPC0geHRzKHg9ZGF0YV9pbl9jbGVhbmVkJEZFQVRVUkVfMixvcmRlci5ieSA9IGRhdGVfdHNfY2xlYW5lZCkKZmVhdHVyZTJfZGlmZl94dHMgPC0gZGlmZi54dHMoKGZlYXR1cmUyX3h0cykpCmxpbmVzKGluZGV4KGZlYXR1cmUyX2RpZmZfeHRzKSwgbG93ZXNzKGZlYXR1cmUyX2RpZmZfeHRzWywxXSwgZiA9IDEvMjAuOCkkeSwgY29sID0gImdyZWVuIiwgbHdkID0gMikKIyMgZmVhdHVyZTJfZGlmZl94dHMgPC0gZmVhdHVyZTJfZGlmZl94dHNbLWMoMSldCgpmZWF0dXJlM194dHMgPC0geHRzKHg9ZGF0YV9pbl9jbGVhbmVkJEZFQVRVUkVfMyxvcmRlci5ieSA9IGRhdGVfdHNfY2xlYW5lZCkKZmVhdHVyZTNfc2NhbGVkX3h0cyA8LSAoKGZlYXR1cmUzX3h0cykpLzEwMApsaW5lcyhpbmRleChmZWF0dXJlM19zY2FsZWRfeHRzKSwgbG93ZXNzKGZlYXR1cmUzX3NjYWxlZF94dHNbLDFdLCBmID0gMS8yMC44KSR5LCBjb2wgPSAib3JhbmdlIiwgbHdkID0gMikKIyMgZmVhdHVyZTNfc2NhbGVkX3h0cyA8LSAgZmVhdHVyZTNfc2NhbGVkX3h0c1stYygxKV0KCmJpbmFyeWZfeHRzIDwtIHh0cyh4PWRhdGFfaW5fY2xlYW5lZCRCSU5BUllfRkVBVFVSRSxvcmRlci5ieSA9IGRhdGVfdHNfY2xlYW5lZCkKIyMgYmluYXJ5Zl94dHMgPC0gYmluYXJ5Zl94dHNbLWMoMSldCmxpbmVzKGluZGV4KGJpbmFyeWZfeHRzKSxiaW5hcnlmX3h0cywgY29sID0gInllbGxvdyIsIGx3ZCA9IDIpCmBgYAoKIyMgQ3Jvc3MgY29yciBiZXR3ZWVuIHRpbWUgc2VyaWVycwpgYGB7cn0KdGl0bGVfbG9ncmV0IDwtICJEYWlseSBsb2cgcmV0dXJucyAoJSkiCnBhcihtZnJvdyA9IGMoMiwgMikpClRTQTo6YWNmKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gdGl0bGVfbG9ncmV0KQpwYWNmKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkLCBuYS5hY3Rpb24gPSBuYS5wYXNzLGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKCgojIChQKUFDRiBvZiBhYnNvbHV0ZSB2YWx1ZSBvZiBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQp0aXRsZV9hYnNsb2dyZXQgPC0gIkRhaWx5IGFicyBsb2cgcmV0dXJucyAoJSkiClRTQTo6YWNmKGFicyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZCksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSB0aXRsZV9hYnNsb2dyZXQpCnBhY2YoYWJzKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9ICIiKQojIChQKUFDRiBvZiBzcXVhcmVkIGRhaWx5IHJldHVybnMKI3BhcihtZnJvdyA9IGMoMSwgMikpCiNUU0E6OmFjZihJKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkXjIpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBtYWluID0gdGl0bGVfc3ApCiNwYWNmKEkobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWReMiksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIG1haW4gPSB0aXRsZV9zcCkKCmBgYAoKYGBge3J9CnRpdGxlX2xvZ3JldCA8LSAiRmVhdHVyZTEgbG9nIHJldHVybnMiCnBhcihtZnJvdyA9IGMoMiwgMikpClRTQTo6YWNmKGZlYXR1cmUxX2RpZmZfeHRzLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gdGl0bGVfbG9ncmV0KQpwYWNmKGZlYXR1cmUxX2RpZmZfeHRzLCBuYS5hY3Rpb24gPSBuYS5wYXNzLGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKCgojIChQKUFDRiBvZiBhYnNvbHV0ZSB2YWx1ZSBvZiBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQp0aXRsZV9hYnNsb2dyZXQgPC0gIkZlYXR1cmUxIGFicyBsb2cgcmV0dXJucyIKVFNBOjphY2YoYWJzKGZlYXR1cmUxX2RpZmZfeHRzKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2Fic2xvZ3JldCkKcGFjZihhYnMoZmVhdHVyZTFfZGlmZl94dHMpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gIiIpCmBgYAoKCmBgYHtyfQp0aXRsZV9sb2dyZXQgPC0gIkZlYXR1cmUyIGxvZyByZXR1cm5zIgpwYXIobWZyb3cgPSBjKDIsIDIpKQpUU0E6OmFjZihmZWF0dXJlMl9kaWZmX3h0cywgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2xvZ3JldCkKcGFjZihmZWF0dXJlMl9kaWZmX3h0cywgbmEuYWN0aW9uID0gbmEucGFzcyxsYWcubWF4ID0gMTAwLCBtYWluID0gIiIpCgoKIyAoUClBQ0Ygb2YgYWJzb2x1dGUgdmFsdWUgb2YgZGFpbHkgcmV0dXJucwojcGFyKG1mcm93ID0gYygxLCAyKSkKdGl0bGVfYWJzbG9ncmV0IDwtICJGZWF0dXJlMiBhYnMgbG9nIHJldHVybnMiClRTQTo6YWNmKGFicyhmZWF0dXJlMl9kaWZmX3h0cyksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSB0aXRsZV9hYnNsb2dyZXQpCnBhY2YoYWJzKGZlYXR1cmUyX2RpZmZfeHRzKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9ICIiKQpgYGAKCmBgYHtyfQp0aXRsZV9sb2dyZXQgPC0gIkZlYXR1cmUzIGxvZyByZXR1cm5zIgpwYXIobWZyb3cgPSBjKDIsIDIpKQpUU0E6OmFjZihmZWF0dXJlM19zY2FsZWRfeHRzLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gdGl0bGVfbG9ncmV0KQpwYWNmKGZlYXR1cmUzX3NjYWxlZF94dHMsIG5hLmFjdGlvbiA9IG5hLnBhc3MsbGFnLm1heCA9IDEwMCwgbWFpbiA9ICIiKQoKCiMgKFApQUNGIG9mIGFic29sdXRlIHZhbHVlIG9mIGRhaWx5IHJldHVybnMKI3BhcihtZnJvdyA9IGMoMSwgMikpCnRpdGxlX2Fic2xvZ3JldCA8LSAiRmVhdHVyZTMgYWJzIGxvZyByZXR1cm5zIgpUU0E6OmFjZihhYnMoZmVhdHVyZTNfc2NhbGVkX3h0cyksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSB0aXRsZV9hYnNsb2dyZXQpCnBhY2YoYWJzKGZlYXR1cmUzX3NjYWxlZF94dHMpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gIiIpCmBgYAoKCiMjIFBsb3QgcmV0IHZzIGYxIGYyIGYzCmBgYHtyfQpwYXIobWZyb3cgPWMoMSwzKSApCgptYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF8gPC0gbWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRbLWMoMSldCmZlYXR1cmUxX2RpZmZfeHRzXyA8LSBmZWF0dXJlMV9kaWZmX3h0c1stYygxKV0KcGxvdChhcy5udW1lcmljKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkXykgfiBhcy5udW1lcmljKGZlYXR1cmUxX2RpZmZfeHRzXykgLHhsYWI9IkZlYXR1cmUxIiwKICAgICB5bGFiPSJEYWlseSAlIGxvZyByZXR1cm5zIikKbGluZXMobG93ZXNzKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRfKSB+IGFzLm51bWVyaWMoZmVhdHVyZTFfZGlmZl94dHNfKSwgZj0wLjEpLAogICAgICBjb2w9InJlZCIpCgpmZWF0dXJlMl9kaWZmX3h0c18gPC0gZmVhdHVyZTJfZGlmZl94dHNbLWMoMSldCnBsb3QoYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZCkgfiBhcy5udW1lcmljKGZlYXR1cmUyX2RpZmZfeHRzKSAseGxhYj0iRmVhdHVyZTIiLAogICAgIHlsYWI9IkRhaWx5ICUgbG9nIHJldHVybnMiKQpsaW5lcyhsb3dlc3MoYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF8pIH4gYXMubnVtZXJpYyhmZWF0dXJlMl9kaWZmX3h0c18pLCBmPTAuMSksCiAgICAgIGNvbD0icmVkIikKCmZlYXR1cmUzX3NjYWxlZF94dHNfIDwtIGZlYXR1cmUzX3NjYWxlZF94dHNbLWMoMSldCnBsb3QoYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF8pIH4gYXMubnVtZXJpYyhmZWF0dXJlM19zY2FsZWRfeHRzXykgLHhsYWI9IkZlYXR1cmUzIiwKICAgICB5bGFiPSJEYWlseSAlIGxvZyByZXR1cm5zIikKbGluZXMobG93ZXNzKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRfKSB+IGFzLm51bWVyaWMoZmVhdHVyZTNfc2NhbGVkX3h0c18pLCBmPTAuMSksCiAgICAgIGNvbD0icmVkIikKCmBgYAoKCmBgYHtyfQpzZWxfZml0IDwtIGF1dG8uYXJpbWEobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRfLCB4cmVnID0gY2JpbmQoZmVhdHVyZTJfZGlmZl94dHNfICAsZmVhdHVyZTNfc2NhbGVkX3h0c18pKQpgYGAKCmBgYHtyfQpwbG90KHJlc2lkKHNlbF9maXQpKQpgYGAKCgpgYGB7cn0KQm94LnRlc3QocmVzaWQoc2VsX2ZpdCksIGxhZz0xMDAsIHR5cGU9IkxqdW5nIikKYGBgCmBgYHtyfQpxcW5vcm0ocmVzaWQoc2VsX2ZpdCwgdHlwZT0iaW5ub3ZhdGlvbiIpKQpgYGAKYGBge3J9Cm1hcmtldGNsb3NlX3RzIDwtIHRzKGFzLm51bWVyaWMoZGF0YV9pbl9jbGVhbmVkJE1BUktFVENMT1NFKSwgc3RhcnQ9ZGF0ZV90c19jbGVhbmVkWzFdLGZyZXF1ZW5jeSA9IDI1MS42MjUpCm1hcmtldGNsb3NlX3RzX2RlY29tcCA8LSBzdGwobG9nKG1hcmtldGNsb3NlX3RzKSwgcy53aW5kb3c9MjEsIHJvYnVzdD1UUlVFKQojbm9fdHJhbnNmb3JtXzIwMDM0NTYgPC0gbXN0bCh3ZWVrbHlfYXZnX21hcmtldGNsb3NlXzIwMDM0NTYpCmZvcmVjYXN0OjphdXRvcGxvdChtYXJrZXRjbG9zZV90c19kZWNvbXApCiNUaW1lU2VyaWVzV2Vla2x5RGVjb21wb3NlZDwtc3RsKHRzX2RhdGEgLCBzLndpbmRvdz0icGVyaW9kaWMiKQpgYGAKCmBgYHtyfQpmZWF0dXJlMV90cyA8LSB0cyhhcy5udW1lcmljKGZlYXR1cmUxX3h0cyksIHN0YXJ0PWRhdGVfdHNbMV0sZnJlcXVlbmN5ID0gMjUxLjYyNSkKZmVhdHVyZTFfdHNfZGVjb21wIDwtIHN0bChsb2coZmVhdHVyZTFfdHMpLCBzLndpbmRvdz0icGVyaW9kaWMiLCByb2J1c3Q9VFJVRSkKI25vX3RyYW5zZm9ybV8yMDAzNDU2IDwtIG1zdGwod2Vla2x5X2F2Z19tYXJrZXRjbG9zZV8yMDAzNDU2KQphdXRvcGxvdChmZWF0dXJlMV90c19kZWNvbXApCmBgYAoKYGBge3J9CmZlYXR1cmUyX3RzIDwtIHRzKGFzLm51bWVyaWMoZmVhdHVyZTJfeHRzKSwgc3RhcnQ9ZGF0ZV90c1sxXSxmcmVxdWVuY3kgPSAyNTEuNjI1KQpmZWF0dXJlMl90c19kZWNvbXAgPC0gc3RsKGxvZyhmZWF0dXJlMl90cyksIHMud2luZG93PSJwZXJpb2RpYyIsIHJvYnVzdD1UUlVFKQojbm9fdHJhbnNmb3JtXzIwMDM0NTYgPC0gbXN0bCh3ZWVrbHlfYXZnX21hcmtldGNsb3NlXzIwMDM0NTYpCmF1dG9wbG90KGZlYXR1cmUyX3RzX2RlY29tcCkKYGBgCgpgYGB7cn0KI21vbnRocGxvdChtYXJrZXRjbG9zZV90c19kZWNvbXAsY2hvaWNlPSJzZWFzb25hbCIpCiNwbG90KHNlYXNvbmFsKGZlYXR1cmUxX3RzX2RlY29tcCkpCiNsaW5lcyhzZWFzb25hbChtYXJrZXRjbG9zZV90c19kZWNvbXApLGNvbD0yKQpwbG90KGZvcmVjYXN0OjpyZW1haW5kZXIobWFya2V0Y2xvc2VfdHNfZGVjb21wKSkKZmVhdHVyZTFfdHMgPC0gdHMoZGF0YV9pbl9jbGVhbmVkJEZFQVRVUkVfMSxzdGFydCA9IGRhdGVfdHNfY2xlYW5lZFsxXSxmcmVxdWVuY3kgPSAyNTEuNjI1KQojbGluZXMoLShmZWF0dXJlMV90cykvMTAwLGNvbD0yKQpmZWF0dXJlMl90cyA8LSB0cyhkYXRhX2luX2NsZWFuZWQkRkVBVFVSRV8yLHN0YXJ0ID0gZGF0ZV90c19jbGVhbmVkWzFdLGZyZXF1ZW5jeSA9IDI1MS42MjUpCiNsaW5lcygtKGZlYXR1cmUyX3RzKS8xMDAsY29sPSJncmVlbiIpCmZlYXR1cmUzX3RzIDwtIHRzKGRhdGFfaW5fY2xlYW5lZCRGRUFUVVJFXzMsc3RhcnQgPSBkYXRlX3RzX2NsZWFuZWRbMV0sZnJlcXVlbmN5ID0gMjUxLjYyNSkKbGluZXMoKChmZWF0dXJlM190cykvMTAwMDApLGNvbD0iYmx1ZSIpCgojbGluZXMoKChmZWF0dXJlM190cy8xMDAwKSleezEvM30vMTAsY29sPSJibHVlIikKI2xpbmVzKHJlbWFpbmRlcihtYXJrZXRjbG9zZV90c19kZWNvbXApKQpgYGAKCgpgYGB7cn0KCnJlc2lkX2RlY29tcCA8LSBmb3JlY2FzdDo6cmVtYWluZGVyKG1hcmtldGNsb3NlX3RzX2RlY29tcCkKcmVzaWRfZGVjb21wX2xvZ3JldCA8LSBkaWZmKChyZXNpZF9kZWNvbXApKQp0aXRsZV9sb2dyZXQgPC0gIkRhaWx5IGxvZyByZXR1cm5zICglKSIKcGFyKG1mcm93ID0gYygyLCAyKSkKVFNBOjphY2YocmVzaWRfZGVjb21wX2xvZ3JldCwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2xvZ3JldCkKcGFjZihyZXNpZF9kZWNvbXBfbG9ncmV0LCBuYS5hY3Rpb24gPSBuYS5wYXNzLGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKCgojIChQKUFDRiBvZiBhYnNvbHV0ZSB2YWx1ZSBvZiBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQp0aXRsZV9hYnNsb2dyZXQgPC0gIkRhaWx5IGFicyBsb2cgcmV0dXJucyAoJSkiClRTQTo6YWNmKGFicyhyZXNpZF9kZWNvbXBfbG9ncmV0KSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2Fic2xvZ3JldCkKcGFjZihhYnMocmVzaWRfZGVjb21wX2xvZ3JldCksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKIyAoUClBQ0Ygb2Ygc3F1YXJlZCBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQojVFNBOjphY2YoSShtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF4yKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbWFpbiA9IHRpdGxlX3NwKQojcGFjZihJKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkXjIpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBtYWluID0gdGl0bGVfc3ApCgpgYGAKCmBgYHtyfQpwbG90KGFzLm51bWVyaWMocmVzaWRfZGVjb21wX2xvZ3JldCkgfiBhcy5udW1lcmljKGRpZmYoZmVhdHVyZTJfdHMpKSx4bGltPWMoLTEsMSkpCmxpbmVzKGxvd2Vzcyhhcy5udW1lcmljKHJlc2lkX2RlY29tcF9sb2dyZXQpIH4gYXMubnVtZXJpYyhkaWZmKGZlYXR1cmUyX3RzKSksIGY9MS81KSwKICAgICAgY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYApgYGB7cn0KcGxvdChhcy5udW1lcmljKHJlc2lkX2RlY29tcF9sb2dyZXQpIH4gYXMubnVtZXJpYygoZmVhdHVyZTNfdHMpLzEwMClbLWMoMSldLHhsaW09YygtMSwxKSkKbGluZXMobG93ZXNzKGFzLm51bWVyaWMocmVzaWRfZGVjb21wX2xvZ3JldCkgfiBhcy5udW1lcmljKChmZWF0dXJlM190cykvMTAwKVstYygxKV0sIGY9MS81KSwKICAgICAgY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKYGBge3J9CnBsb3QoYXMubnVtZXJpYyhyZXNpZF9kZWNvbXBfbG9ncmV0KSB+IGFzLm51bWVyaWMoZGlmZihmZWF0dXJlMV90cykpLHhsaW09YygtMSwxKSkKbGluZXMobG93ZXNzKGFzLm51bWVyaWMocmVzaWRfZGVjb21wX2xvZ3JldCkgfiBhcy5udW1lcmljKGRpZmYoZmVhdHVyZTFfdHMpKSwgZj0xLzUpLAogICAgICBjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgpgYGB7cn0KZm9yX3N0bCA8LSBmb3JlY2FzdChtYXJrZXRjbG9zZV90c19kZWNvbXAsIGggPSAyMSkKcGxvdChmb3Jfc3RsKQpgYGAKYGBge3J9CmV4cChmb3Jfc3RsJG1lYW4pCmBgYAoKCmBgYHtyfQp0YWlsKG1hcmtldGNsb3NlX3RzKQpgYGAKCgoKIyAgU2VjdGlvbiBJSUk6IE1vZGVsIERldmVsb3BtZW50IGFuZCBhbmFseXNpcyAoUikKIyMjIyBEYWlseSBWb2xhdGlsaXR5IGJhc2VkIFRyZW5kIEZvbGxvd2luZyBhbmQgTWVhbiBSZXZlcnNpb24gU3dpdGNoaW5nCgooUGxlYXNlIGNsZWFyIHRoZSBlbnZpcm9ubWVudCBhbmQgYWxsIHZhcmlhYmxlcywgc3RhcnRpbmcgd2l0aCBhIGNsZWFuIHNsYXRlIGZyb20gaGVyZS4pCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KSSBoYXZlbid0IGNvbnNpZGVyZWQgdGhlIHByb3ZpZGVkIGZlYXR1cmVzIGluIHRoZSBkYXRhIGZvciB0aGlzIG1vZGVsIGRldmVsb3BtZW50IHNpbmNlIEkgZG9uJ3Qga25vdyB3aGF0IHRoZXkgcmVwcmVzZW50LCBob3cgdGhleSB3ZXJlIGV4dHJhY3RlZCBhbmQgd2hldGhlciBvciBub3QgdGhleSB3ZXJlIGxhZ2dlZCBieSBvbmUgdHJhZGluZyBkYXkuIEZ1cnRoZXIgaXQgc2VlbXMgYWxsIG9mIHRoZW0gYXJlIGVuZG9nZW5vdXMgKHNpbmNlIHRoZXkgc2VlbSB0byBiZSBleHRyYWN0ZWQgZnJvbSB0aGUgcHJpY2UgZGF0YSksIHNvIHRoZXkgc2hvdWxkbid0IGhhdmUgYW55IGV4dHJhICdpbmZvcm1hdGlvbicgKGluIGluZm9ybWF0aW9uIHRoZW9yZXRpY2FsIHNlbnNlIG9mIHRoZSB3b3JkKSBldmVuIHRob3VnaCB0aGV5IG1pZ2h0IGJlIGJldHRlciBwcmVkaWN0b3JzLiBIb3dldmVyLCBoZXJlIEkgZm9jdXMgb24gaW50ZXJwcmV0YWJpbGl0eSBvZiB0aGUgc3RyYXRlZ3kgaW4gdGVybXMgb2YgdmFyaWFibGVzIHdlbGwga25vd24gaW4gZmluYW5jaWFsIHRyYWRpbmcuCiAKIApUbyBlbmFibGUgaW50ZXJwcmV0YWJpbGl0eSBvZiB0aGUgbW9kZWwsIEkgdXNlIHdlbGwtZGVmaW5lZCBmZWF0dXJlcyBvciBmZWF0dXJlcyByZXByZXNlbnRpbmcgd2VsbC1kZWZpbmVkIHF1YW50aXRpZXMgcmVsYXRlZCB0byBhIHN0b2NrIHByaWNlIGVnLiB2b2xhdGlsaXR5LCBtb3ZpbmcgYXZlcmFnZXMsIHJlbGF0aXZlIHN0cmVuZ3RoIGluZGV4IGV0Yy4gVGhlIG1vZGVsIGVzc2VudGlhbGx5IHN3aXRjaGVzIGJldHdlZW4gZm9sbG93aW5nIGEgdHJlbmQgYW5kIG1lYW4gcmV2ZXJzaW9uIGJhc2VkIG9uIHRoZSBzdG9jayB2b2xhdGlsaXR5IChyZWxhdGl2ZSB0byBpdHMgaGlzdG9yaWNhbCB2YWx1ZXMpLiAKIApUaGUgYmFzaWMgc3RyYXRlZ3kgaXMgdGhpczogd2hlbiB0aGUgc3RvY2sgdm9sYXRpbGl0eSBpcyBoaWdoIHJlbGF0aXZlIHRvIGl0cyB1c3VhbCB2b2xhdGlsaXR5LCBJIHVzZSBtZWFuIHJldmVyc2lvbiBzdHJhdGVneSBiYXNlZCBvbiBSU0koMikuIFdoZW4gdGhlIHZvbGF0aWxpdHkgaXMgbG93IEkgZm9sbG93IHRoZSB0cmVuZCBiYXNlZCBvbiBNQS4gSW4gYWRkaXRpb24gaW4gc2xvdyB2b2xhdGlsaXR5IHJlZ2ltZSBpZiB0aGUgUlNJKDIpIGdldHMgdmVyeSBoaWdoLCB0aGlzIGluZGljYXRlcyBvdmVyc29sZCBjb25kaXRpb24gaW4gd2hpY2ggY2FzZSBJIHNob3J0LiBJIGZ1cnRoZXIgc2hvdyBpbXByb3ZlbWVudCBieSB1cGRhdGluZyB0aGUgdGhyZXNob2xkIHBhcmFtZXRlcnMgZXZlcnkgNSBkYXlzIGJ5IGlkZW50aWZ5aW5nIGJlc3QgbW9kZWwgcGFyYW1ldGVycyB1c2luZyBncmlkIHNlYXJjaC4gUGVyZm9ybWFuY2UgaXMganVkZ2VkIG9uIHRoZSBiYXNpcyBvZiBjdW11bGF0aXZlIFBOTCB3cnQgYWx3YXlzIGJ1eSBzdHJhdGVneS4gVGhlIHByZWRpY3Rpb24gZm9yIG5leHQgYWxzbyBpbXByb3ZlcyBhbmQgaXMgLTEgZnJvbSB0aGUgdXBkYXRpbmcgc3RyYXRlZ3kuCiAKVGhpcyBzdHJhdGVneSBjYW4gYmUgcG90ZW50aWFsbHkgZnVydGhlciBpbXByb3ZlZCBieSB1c2luZyBtb21lbnR1bSwgd2hpY2ggSSBleHBlY3QgdG8gYmUgcGFydGljdWxhcmx5IHN0cm9uZyBkdXJpbmcgZG93bnR1cm5zLiBCdXQgaXQgaGFzIHRvIGJlIGJhbGFuY2VkIGFnYWluc3QgYmVpbmcgJ3Rvby1sYXRlJyB0cnlpbmcgdG8gdGltZSB0aGUgc3RvY2sgcHJpY2UuIEFub3RoZXIgcG90ZW50aWFsIGltcHJvdmVtZW50IGlzIHRvIHVzZSB2b2xhdGlsaXR5IGZvcmVjYXN0IHVzaW5nIGEgTWFya292IHN3aXRjaGluZyBiYXNlZCBHQVJDSCBtb2RlbCAoc2luY2UgdGhleSBzZWVtIHRvIGhhdmUgYmVlbiBzaG93biBtb3JlIGFjY3VyYXRlIGluIHByZWRpY3Rpbmcgc2hvcnQgdGVybSB2b2xhdGlsaXR5IDx3ZWVrIHRoYW4gZ2VuZXJhbCBHQVJDSCBtb2RlbHMpIG9yIEdBUkNILUFSSU1BIG1vZGVsLiBJIHVzZSBSIGhlcmUgaW5zdGVhZCBvZiBweXRob24sIGhvd2V2ZXIgYSBzbWFsbCBzbmlwcGV0IG9mIGNvZGUgdG8gY2xlYXVwIHRoZSBkYXRhIGlzIGluIHB5dGhvbjMuCgpgYGB7cn0Kcm0obGlzdD1scygpKQojIyBTZXR1cAojIyBHZXQgc29tZSBmdW5jdGlvbnMvcGFja2FnZXMKIyMgaW5zdGFsbC5wYWNrYWdlcygiUkN1cmwiKQpyZXF1aXJlKFJDdXJsKQpzaXQgPSBnZXRVUkxDb250ZW50KCdodHRwczovL2dpdGh1Yi5jb20vc3lzdGVtYXRpY2ludmVzdG9yL1NJVC9yYXcvbWFzdGVyL3NpdC5neicsIGJpbmFyeT1UUlVFLCBmb2xsb3dsb2NhdGlvbiA9IFRSVUUsIHNzbC52ZXJpZnlwZWVyID0gRkFMU0UpCmNvbiA9IGd6Y29uKHJhd0Nvbm5lY3Rpb24oc2l0LCAncmInKSkKc291cmNlKGNvbikKY2xvc2UoY29uKQpgYGAKCmBgYHtyfQojIyBHZXQgZGF0YSwgdGhpcyBpcyBvcmlnaW5hbCBmaWxlIGRvd25sb2FkZWQgd2l0aG91dCBhbnkgY2hhbmdlcwpzZXR3ZCgifi9Eb2N1bWVudHMvaW50ZXJ2aWV3X3ByZXAvUXVlc3Rpb24xIikKZGF0YV9pbiA8LSByZWFkLmNzdihmaWxlID0gJ0RhdGFTZXQuY3N2JykKZGF0ZV90cyA8LSBhcy5EYXRlKGRhdGFfaW4kREFURSwgZm9ybWF0ID0gIiVtLyVkLyVZIikKbGlicmFyeSh4dHMpCm1hcmtldGNsb3NlX3h0cyA8LSB4dHMoZGF0YV9pbiRNQVJLRVRDTE9TRSxvcmRlci5ieSA9IGRhdGVfdHMpCm1hcmtldGNsb3NlX3JldCA8LSAxMDAqZGlmZihsb2cobWFya2V0Y2xvc2VfeHRzKSkKCmBgYAoKYGBge3J9CiNFc3RpbWF0ZSBoaXN0b3JpY2FsIHJlbGF0aXZlIHZvbGF0aWxpdHkKbGlicmFyeShxdWFudG1vZCkKcmV0LmxvZyA8LSBST0MobWFya2V0Y2xvc2VfeHRzLCB0eXBlPSdjb250aW51b3VzJykKaGlzdC52b2wgPC0gcnVuU0QocmV0LmxvZywgbiA9IDIxKQp2b2wucmFuayA8LSBwZXJjZW50LnJhbmsoU01BKHBlcmNlbnQucmFuayhoaXN0LnZvbCwgMjUyKSwgMjEpLCAyNTApCmBgYAoKCmBgYHtyfQojIyBNZWFuIHJldmVyc2lvbiBmZWF0dXJlCnJzaTJfdmFsdWVzIDwtIFJTSShtYXJrZXRjbG9zZV94dHMsMikKCiMjIFRyZW5kIGZvbGxvd2luZyBmZWF0dXJlCnNtYS5zaG9ydCA8LSAgU01BKG1hcmtldGNsb3NlX3h0cywgNSkKc21hLmxvbmcgPC0gU01BKG1hcmtldGNsb3NlX3h0cywgMjApCgpgYGAKCgpgYGB7cn0KIyMgVHJhZGluZyBzdHJhdGVneTogSGlnaCBWb2xhdGlsaXR5IC0gTWVhbiByZXZlcnNpb24sIExvdyB2b2xhdGlsaXR5IC0gZm9sbG93IHRyZW5kCiMgbG9uZyBpZiB2b2wucmFuayA+IDAuNTAgYW5kIHJzaTJfdmFsdWVzIDwgNTAgKG1lYW4gcmV2ZXJzaW9uKSBzaG9ydCBvdGhlcndpc2UKIyBzaG9ydCBpZiB2b2wucmFuayA8IDAuNTAgYW5kIAojIHJzaTJfdmFsdWVzID4gODAgKG92ZXJib3VnaHQpIG9yIDUgd2VlayBNQSA8IDIwIHdlZWsgTUEKIyBsb25nIG90aGVyd2lzZQojIEFkZCBsYWcgdG8gcHJlZGljdCBuZXh0IHZhbHVlIGZyb20gY3VycmVudCBlc3RpbWF0ZXMKIyAKIyBgaWlmYCBpcyBtb3JlIHN0YWJsZSB2ZXJzaW9uIG9mIGBpZmVsc2UoKWAgZnVuY3Rpb24gaW4gUiB0byBncmFjZWZ1bGx5IGhhbmRsZSBgTkFgIGFuZCBgSW5mYCBlbnRyaWVzCgpzaWcgPC0gTGFnKGlpZih2b2wucmFuayA+IDAuNTAsIAogICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXMgPCA1MCAsIDEsIC0xKSwKICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydCA8IHNtYS5sb25nIHwgcnNpMl92YWx1ZXMgPiA4MCAsIC0xLCAxKQopKQoKYGBgCgpgYGB7cn0KIyMgRXZhbHVhdGUKIyBXZSBldmFsdWF0ZSB0aGUgc3RyYXRlZ3kgYWdhaW5zdCBhbHdheXMgYnV5IHN0cmF0ZWd5IGluIGNvbHVtbiBDVU1TVU0gaW4gZGF0YS4gTW9tZW50dW0gaXMganVzdCB0aGUgZGlmZmVyZW5jZSBvZiBzdWNjZXNzaXZlIGVudHJpZXMuIE5vdGluZyBoZXJlIHRoYXQgc2lnIGlzIGRlbGF5ZWQgYnkgMSB0cmFkaW5nIGRheS4KcHJpbnQoJ0NVTVNVTSBjb2x1bW4gZnJvbSBkYXRhOicpCnByaW50KGhlYWQoZGF0YV9pbiRDVU1TVU1bLWMoMSldKSkKcHJpbnQoIkFsd2F5cyBidXkgc3RyYXRlZ3kiKQpyZXQgPC0gMTAwMCptb21lbnR1bShtYXJrZXRjbG9zZV94dHMpKjEKcHJpbnQoaGVhZChjdW1zdW0ocmV0Wy1jKDEpXSkpKQoKYGBgCgpgYGB7cn0KIyMgUGxvdCBjdW11bGF0aXZlIFBOTApyZXQgPC0gMTAwMCptb21lbnR1bShtYXJrZXRjbG9zZV94dHMpKnNpZwplcSA8LSAoY3Vtc3VtKHJldFstYygxKV0pKQpwbG90KGNvcmVkYXRhKGVxKX5kYXRlX3RzWy1jKDEpXSx0eXBlPSdsJyx4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpncmlkKGNvbD0xLGx3ZD0xKQpsaW5lcyhkYXRhX2luJENVTVNVTVstYygxKV1+ZGF0ZV90c1stYygxKV0sY29sPSJyZWQiKQpsZWdlbmQoeD0idG9wbGVmdCIsbGVnZW5kPWMoIlZvbGF0aWxpdHkgYmFzZWQgc3dpdGNoaW5nIiwiQnV5IERhaWx5IChHaXZlbikiKSwKICAgICAgIGZpbGw9YygiYmxhY2siLCJyZWQiKSkKCmBgYAoKYGBge3J9CiMjIENvbXBhcmUgY3VtdWxhdGl2ZSBQTkwgYXQgdGhlIGVuZApwcmludChkYXRhLmZyYW1lKCdHaXZlbiBTdHJhdGVneSc9dGFpbChkYXRhX2luJENVTVNVTVstYygxKV0pLCdQcm9wb3NlZCBTdHJhdGVneSc9dGFpbChhcy52ZWN0b3IoZXEpKSxyb3cubmFtZXMgPSBhcy5jaGFyYWN0ZXIodGFpbChkYXRlX3RzKSkpKQpgYGAKCmBgYHtyfQojIyBPcHRpbWl6ZSB0aHJlc2hvbGRpbmcgcGFyYW1ldGVycyB1c2luZyBncmlkIHNlYXJjaDoKIyBQYXJhbWV0ZXJzOiB2b2wucmFua19NSU4sIHJzaTJfdmFsdWVzX01BWCwgcnNpMl92YWx1ZXNfTUlOCiMgc2lnLnRlc3QgPC0gIChpaWYodm9sLnJhbmsudGVzdCA+IHZvbC5yYW5rX01JTiwgCiMgICAgICAgICAgICAgICAgICAgaWlmKChyc2kyX3ZhbHVlcy50ZXN0IDwgcnNpMl92YWx1ZXNfTUFYKSAsIDEsIC0xKSwKIyAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0LnRlc3QgPCBzbWEubG9uZy50ZXN0IHwgKChyc2kyX3ZhbHVlcy50ZXN0ID4gcnNpMl92YWx1ZXNfTUlOKSksIC0xLCAxKQoKIyB3ZSB3aWxsIHVzZSBmb3JlYWNoIGZvciBmYXN0ZXIgZm9yIGxvb3BzCiMgYXV0b21hdGljIGluc3RhbGwgb2YgcGFja2FnZXMgaWYgdGhleSBhcmUgbm90IGluc3RhbGxlZCBhbHJlYWR5Cmxpc3Qub2YucGFja2FnZXMgPC0gYygKICAiZm9yZWFjaCIsCiAgImRvUGFyYWxsZWwiLAogICJyYW5nZXIiLAogICJwYWxtZXJwZW5ndWlucyIsCiAgInRpZHl2ZXJzZSIsCiAgImthYmxlRXh0cmEiCikKCm5ldy5wYWNrYWdlcyA8LSBsaXN0Lm9mLnBhY2thZ2VzWyEobGlzdC5vZi5wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpWywiUGFja2FnZSJdKV0KCmlmKGxlbmd0aChuZXcucGFja2FnZXMpID4gMCl7CiAgaW5zdGFsbC5wYWNrYWdlcyhuZXcucGFja2FnZXMsIGRlcD1UUlVFKQp9CgojbG9hZGluZyBwYWNrYWdlcwpmb3IocGFja2FnZS5pIGluIGxpc3Qub2YucGFja2FnZXMpewogIHN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcygKICAgIGxpYnJhcnkoCiAgICAgIHBhY2thZ2UuaSwgCiAgICAgIGNoYXJhY3Rlci5vbmx5ID0gVFJVRQogICAgKQogICkKfQoKYGBgCgoKYGBge3J9CiMgQ3JlYXRlIHN1YnNldCBhcnJheXMgdG8gdGVzdCBob3cgbXVjaCB0aGVzZSBwYXJhbWV0ZXJzIGNoYW5nZSBvdmVyIHRpbWUKIyBFdmVyeSA1IGRheSB1cGRhdGUgb2YgcGFyYW1ldGVycwpzdWJfc2VyLmxlbiA8LSA1CnN0YXJ0X3BvaW50IDwtIDcwMApUbWF4X2FycmF5IDwtIHNlcShzdGFydF9wb2ludCxsZW5ndGgobWFya2V0Y2xvc2VfeHRzKSxzdWJfc2VyLmxlbikKCiMgR3JpZCBzZWFyY2ggZm9yIHBhcmFtZXRycyB0aGF0IG1heGltaXplIGN1bXVsYXRpdmUgcmV0dXJuIG92ZXIgc3Vic2V0IGFycmF5CiMgYW5kIHN0b3JlIHRoZSBwcmVkaWN0aW9uIGZvciBuZXh0IGRheQpyZXR1cm5fbWF4LnRlc3QgPC0gbWF0cml4KC1JbmYsbGVuZ3RoKFRtYXhfYXJyYXkpLDEpCnByZWRpY3RfYXJyYXkudGVzdCA8LSBtYXRyaXgoMSxsZW5ndGgoVG1heF9hcnJheSksMSkKbWF4X3Bhci50ZXN0IDwtIG1hdHJpeCgwLGxlbmd0aChUbWF4X2FycmF5KSwzKQpgYGAKCgoKYGBge3J9CiMgUmVhZCBmcm9tIHNhdmVkIGZpbGUgZm9yIHJldGVzdGluZwptYXhfcGFyLnRlc3QgPC0gcmVhZC5jc3YoIm1heF9wYXIudGVzdF81ZGF5NzAwXzMwX3JzaTJNSU5yYW5nZS5jc3YiKVssYygiVjEiLCJWMiIsIlYzIildCmBgYAoKCgpgYGB7cn0KCnByaW50KCJTdGFydGluZyBncmlkIHNlYXJjaC4gVGhpcyBtaWdodCB0YWtlIGEgd2hpbGUgKDUtMTAgbWludXRlcykiKQpmb3JlYWNoIChpID0gIDE6bGVuZ3RoKFRtYXhfYXJyYXkpKSAlZG8lIHsKICBwcmludChwYXN0ZSgiVXBkYXRpbmcgdGhyZXNob2xkIHBhcmFtZXRlcnMgZm9yIHRoZSB3ZWVrIixpLCJvZiIsbGVuZ3RoKFRtYXhfYXJyYXkpKSkKICBUbWF4IDwtIFRtYXhfYXJyYXlbaV0KICBtYXJrZXRjbG9zZV94dHMudGVzdCA8LSBtYXJrZXRjbG9zZV94dHNbMTpUbWF4XQogIG1hcmtldGNsb3NlX3JldC50ZXN0IDwtIDEwMCpkaWZmKGxvZyhtYXJrZXRjbG9zZV94dHMudGVzdCkpWy1jKDEpXQogICNIaXN0b3JpY2FsIHZvbGF0aWxpdHkKICByZXQubG9nLnRlc3QgPSBST0MobWFya2V0Y2xvc2VfeHRzLnRlc3QsIHR5cGU9J2NvbnRpbnVvdXMnKQogIGhpc3Qudm9sLnRlc3QgPSBydW5TRChyZXQubG9nLnRlc3QsIG4gPSAyMSkKICB2b2wucmFuay50ZXN0ID0gcGVyY2VudC5yYW5rKFNNQShwZXJjZW50LnJhbmsoaGlzdC52b2wudGVzdCwgMjUyKSwgMjEpLCAyNTApCiAgIyBNZWFuIHJldmVyc2lvbgogIHJzaTJfdmFsdWVzLnRlc3QgPSBSU0kobWFya2V0Y2xvc2VfeHRzLnRlc3QsMikKICAjIFRyZW5kIGZvbGxvd2luZwogIHNtYS5zaG9ydC50ZXN0IDwtICBTTUEobWFya2V0Y2xvc2VfeHRzLnRlc3QsIDUpCiAgc21hLmxvbmcudGVzdCA8LSBTTUEobWFya2V0Y2xvc2VfeHRzLnRlc3QsIDIwKQogICMgR3JpZCBzZWFyY2gKICBmb3JlYWNoICh2b2wucmFua19NSU4gPSBzZXEoMC4xLDAuOCwwLjAxKSkgJWRvJSB7IAogICAgZm9yIChyc2kyX3ZhbHVlc19NQVggaW4gc2VxKDMwLDgwLDIpKSB7CiAgICAgIGZvciAocnNpMl92YWx1ZXNfTUlOIGluIDgwKXsgI3NlcSgzMCw4MCwxMCkpIHsgIyBObyBlZmZlY3Qgb2YgY2hhbmdlcwogICAgICAgICMgUHJlZGljdGlvbiBzaWduYWwgY2FsY3VsYXRpb24KICAgICAgICBzaWcudGVzdCA8LSAgKGlpZih2b2wucmFuay50ZXN0ID4gdm9sLnJhbmtfTUlOLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoKHJzaTJfdmFsdWVzLnRlc3QgPCByc2kyX3ZhbHVlc19NQVgpICwgMSwgLTEpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGlpZihzbWEuc2hvcnQudGVzdCA8IHNtYS5sb25nLnRlc3QgfCAoKHJzaTJfdmFsdWVzLnRlc3QgPiByc2kyX3ZhbHVlc19NSU4pKSwgLTEsIDEpCiAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgKSkKICAgICAgICAjIFNhdmUgdGhpcyBmb3IgZnV0dXJlIHVzZQogICAgICAgIHByZWRpY3QudGVzdCA8LSB0YWlsKHNpZy50ZXN0LDEpCiAgICAgICAgCiAgICAgICAgIyBMYWcgc2lnbmFsIGJ5IG9uZSB0cmFkaW5nIGRheQogICAgICAgIHNpZy50ZXN0IDwtIExhZyhzaWcudGVzdCkKICAgICAgICByZXQudGVzdCA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cy50ZXN0KSpzaWcudGVzdAogICAgICAgIAogICAgICAgIHJldHVybl90b3RhbC50ZXN0IDwtIHRhaWwoY3Vtc3VtKHJldC50ZXN0Wy1jKDEpXSksMSkKCiAgICAgICAgIyBTYXZlIGlmIEN1bXVsYXRpdmUgcmV0dXJuIGlzIG1vcmUgdGhhbiBjdXJyZW50IG1heGltdW0KICAgICAgICBpZiAocmV0dXJuX3RvdGFsLnRlc3QgPiByZXR1cm5fbWF4LnRlc3RbaV0pIHsKICAgICAgICAgIAogICAgICAgICAgcmV0dXJuX21heC50ZXN0W2ldIDwtIHJldHVybl90b3RhbC50ZXN0CiAgICAgICAgICBtYXhfcGFyLnRlc3RbaSwxXSA8LSB2b2wucmFua19NSU4KICAgICAgICAgIG1heF9wYXIudGVzdFtpLDJdIDwtIHJzaTJfdmFsdWVzX01BWAogICAgICAgICAgbWF4X3Bhci50ZXN0W2ksM10gPC0gcnNpMl92YWx1ZXNfTUlOCiAgICAgICAgICBwcmVkaWN0X2FycmF5LnRlc3RbaV0gPC0gcHJlZGljdC50ZXN0CiAgICAgICAgICBzaWcudGVzdC5tYXggPC0gc2lnLnRlc3QKICAgICAgICAgIAogICAgICAgIH0KICAgICAgICAKICAgICAgfQogICAgICAKICAgIH0KICAgIAogIH0KfQojIFNhdmUgZm9yIGZ1dHV0cmUgcmV1c2UKIyB3cml0ZS5jc3YobWF4X3Bhci50ZXN0LCJtYXhfcGFyLnRlc3RfNWRheTcwMF8zMF9yc2kyTUlOcmFuZ2UuY3N2IikKCmBgYAoKYGBge3J9CiMgUHJpbnQgb3B0aW1pemVkIHBhcmFtZXRlcnMgZm9yIGVhY2ggc3ViIHRpbWUgc2VyaWVzCnByaW50KG1heF9wYXIudGVzdCkKYGBgCgpgYGB7cn0Kb3B0aW1pemVkLnBhcnNfNWRheXMgPC0gZGF0YS5mcmFtZSgndm9sLnJhbmtfTUlOJz1tYXhfcGFyLnRlc3RbLDFdLCdyc2kyX3ZhbHVlc19NQVgnPW1heF9wYXIudGVzdFssMl0sJ3JzaTJfdmFsdWVzX01JTic9bWF4X3Bhci50ZXN0WywzXSxyb3cubmFtZXMgPSBkYXRlX3RzW1RtYXhfYXJyYXldKQpwcmludChvcHRpbWl6ZWQucGFyc181ZGF5cykKIyBXZSB3aWxsIHVzZSB2YWx1ZXMgKDAuNjcsNDAsODApIGZvciBkYXRlcyBhZnRlciBhbmQgbm90IGluY2x1ZGluZyAyMDA0LTA5LTEwIGFuZCBiZWZvcmUgYW5kIGluY2x1ZGluZyAyMDA0LTExLTE5IGFuZCBzbyBvbi4KCm9wdGltaXplZC5wYXJzXzVkYXlzIDwtIGRhdGEuZnJhbWUoJ3ZvbC5yYW5rX01JTic9cmVwKG1heF9wYXIudGVzdFsxOmxlbmd0aChUbWF4X2FycmF5KSwxXSxlYWNoPXN1Yl9zZXIubGVuKSwncnNpMl92YWx1ZXNfTUFYJz1yZXAobWF4X3Bhci50ZXN0WzE6bGVuZ3RoKFRtYXhfYXJyYXkpLDJdLGVhY2g9c3ViX3Nlci5sZW4pLCdyc2kyX3ZhbHVlc19NSU4nPXJlcChtYXhfcGFyLnRlc3RbMTpsZW5ndGgoVG1heF9hcnJheSksM10sZWFjaD1zdWJfc2VyLmxlbikpCnByaW50KGRpbSgob3B0aW1pemVkLnBhcnNfNWRheXMpKSkKIyBwcmUtcGVuZCB2YWx1ZXMgZm9yIGZpcnN0IDcwMCBkYXlzCmluaXRpYWwucGFyc183MDBkYXlzIDwtIGRhdGEuZnJhbWUoJ3ZvbC5yYW5rX01JTic9cmVwKC41MCxlYWNoPXN0YXJ0X3BvaW50KSwncnNpMl92YWx1ZXNfTUFYJz1yZXAoNTAsZWFjaD1zdGFydF9wb2ludCksJ3JzaTJfdmFsdWVzX01JTic9cmVwKDgwLGVhY2g9c3RhcnRfcG9pbnQpKQpvcHRpbWl6ZWQucGFyc181ZGF5cyA8LSByYmluZChpbml0aWFsLnBhcnNfNzAwZGF5cyxvcHRpbWl6ZWQucGFyc181ZGF5cykKb3B0aW1pemVkLnBhcnNfNWRheXMgPC0gb3B0aW1pemVkLnBhcnNfNWRheXNbMTpsZW5ndGgobWFya2V0Y2xvc2VfeHRzKSxdCnByaW50KGhlYWQob3B0aW1pemVkLnBhcnNfNWRheXMpKQpwcmludCh0YWlsKG9wdGltaXplZC5wYXJzXzVkYXlzKSkKYGBgCgoKYGBge3J9CiMjIEV2YWx1YXRlIHBlcmZvcm1hbmNlIG9mIG5ldyBwYXJhbWV0ZXJzCiMjIE9wdGltaXplZCB0cmFkaW5nIHNpZ25hbApzaWdfb3B0IDwtIExhZyhpaWYodm9sLnJhbmsgPiBvcHRpbWl6ZWQucGFyc181ZGF5cyR2b2wucmFua19NSU4sIAogICAgICAgICAgICAgICAgICAgaWlmKHJzaTJfdmFsdWVzIDwgb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUFYICAsIDEsIC0xKSwKICAgICAgICAgICAgICAgICAgIGlpZihzbWEuc2hvcnQgPCBzbWEubG9uZyB8IHJzaTJfdmFsdWVzID4gb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUlOLCAtMSwgMSkKKSkKYGBgCgoKYGBge3J9CiMjUGxvdCBvZiBjdW11bGF0aXZlIHJldHVybnMKcmV0X29wdCA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cykqc2lnX29wdAplcV9vcHQgPC0gKGN1bXN1bShyZXRfb3B0Wy1jKDEpXSkpCnBsb3QoY29yZWRhdGEoZXFfb3B0KX5kYXRlX3RzWy1jKDEpXSx0eXBlPSdsJyxjb2w9ImdyZWVuIix4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpsaW5lcyhjb3JlZGF0YShlcSl+ZGF0ZV90c1stYygxKV0pCmxpbmVzKGRhdGFfaW4kQ1VNU1VNWy1jKDEpXX5kYXRlX3RzWy1jKDEpXSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEpCmxlZ2VuZCh4PSJ0b3BsZWZ0IixsZWdlbmQ9YygiNWRheSB1cGRhdGUgc3c6IExpdmUgdHJhZGluZyArIGJhY2t0ZXN0aW5nIiwiSW5pdGFpbCBwYXIgc3dpdGNoaW5nIGJhY2t0ZXN0aW5nIiwiQnV5IERhaWx5IChHaXZlbikiKSwKICAgICAgIGZpbGw9YygiZ3JlZW4iLCJibGFjayIsInJlZCIpKQojQ2xlYXJseSBuZXcgcGFyYW1ldGVycyBwZXJmb3JtIGJldHRlcgpgYGAKCmBgYHtyfQojIFdlIGNhbiBhbHNvIHVzZSB0aGUgZW50aXJldHkgb2YgdGltZSBzZXJpZXMgZm9yIGJhY2t0ZXN0aW5nLCB0byBzZWUgd2hhdCBjb3VsZCBoYXZlIGJlZW4gdGhlIHBlcmZvcm1hbmNlIGlmIHdlIGtuZXcgdGhlIGJlc3QgcGFyYW1ldGVyIGVzdGltYXRlcyBpbiB0aGUgcGFzdCAob3IgdHJ5IGhpdCBhbmQgdHJpYWwvb3RoZXIgb3B0aW1pemF0aW9uIHRvIGltcHJvdmUgb3ZlcmFsbCBwZXJmb3JtYW5jZSB3aXRoIHNpbmdsZSBzZXQgb2YgcGFyYW1ldGVycyBmb3IgYWxsIHRpbWUgcG9pbnRzKSBhbmQgdXNlIGl0IHRvIGZ1cnRoZXIgaW1wcm92ZSB0aGUgdXBkYXRlIGFwcHJvYWNoIApzaWdfb3B0X29wdGltYWwgPC0gTGFnKGlpZih2b2wucmFuayA+IDAuMiwgCiAgICAgICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXMgPCAzNiAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0IDwgc21hLmxvbmcgfCByc2kyX3ZhbHVlcyA+IDgwLCAtMSwgMSkKKSkKcmV0X29wdF9vcHRpbWFsIDwtIDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKSpzaWdfb3B0X29wdGltYWwKZXFfb3B0X29wdGltYWwgPC0gKGN1bXN1bShyZXRfb3B0X29wdGltYWxbLWMoMSldKSkKcGxvdChjb3JlZGF0YShlcV9vcHRfb3B0aW1hbCl+ZGF0ZV90c1stYygxKV0sY29sPSJibHVlIix0eXBlPSdsJyx4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpsaW5lcyhjb3JlZGF0YShlcV9vcHQpfmRhdGVfdHNbLWMoMSldLGNvbD0iZ3JlZW4iKQpsaW5lcyhjb3JlZGF0YShlcSl+ZGF0ZV90c1stYygxKV0pCmxpbmVzKGRhdGFfaW4kQ1VNU1VNWy1jKDEpXX5kYXRlX3RzWy1jKDEpXSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEpCmxlZ2VuZCh4PSJ0b3BsZWZ0IixsZWdlbmQ9YygiNWRheSB1cGRhdGUgc3cgTGl2ZXRyYWRpbmcgKyBCYWNrdGVzdGluZyIsIkluaXRpYWwgcGFyIHN3aXRjaGluZzogQmFja3Rlc3RpbmciLCJCdXkgRGFpbHkgKEdpdmVuKSIsIk9wdGltYWwgcGFyOiBCYWNrdGVzdGluZyIpLAogICAgICAgZmlsbD1jKCJncmVlbiIsImJsYWNrIiwicmVkIiwiYmx1ZSIpKQpgYGAKCmBgYHtyfQojQ29tcGFyZSBjdW11bGF0aXZlIFBOTCBhdCB0aGUgZW5kIGFmdGVyIGltcHJvdmVtZW50cwpwcmludChkYXRhLmZyYW1lKCdHaXZlbiBTdHJhdGVneSc9dGFpbChkYXRhX2luJENVTVNVTVstYygxKV0pLCdGaXhQYXJTd2l0Y2hpbmcnPXRhaWwoYXMudmVjdG9yKGVxKSksJzVkYXlVcGRhdGVTdyc9dGFpbChhcy52ZWN0b3IoZXFfb3B0KSksJ09wdGltYWwnPXRhaWwoYXMudmVjdG9yKGVxX29wdF9vcHRpbWFsKSkscm93Lm5hbWVzID0gYXMuY2hhcmFjdGVyKHRhaWwoZGF0ZV90cykpKSkKCmBgYApgYGB7cn0KIyBQcmVkaWN0aW9ucyBmb3IgbmV4dCBkYXkgdXNpbmcgdGhlc2UgcGFyYW1ldGVyczoKIyBQcmVkaWN0aW9uIGZyb20gb3B0aW1hbCB3aWxsIGJlIHNhbWUgYXMgb3B0IHNpbmNlIHRoZSBwYXJhbWV0ZXJzIGFyZSB0aGUgc2FtZQojIG9uIGxhc3QgZGF5Cmxhc3RkYXlfcm93IDwtIGxlbmd0aChtYXJrZXRjbG9zZV9yZXQpCgpwcmVkaWN0aW9uX2ZpeGVkIDwtIGlpZih2b2wucmFua1tsYXN0ZGF5X3Jvd10gPiAwLjUwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXNbbGFzdGRheV9yb3ddIDwgNTAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydFtsYXN0ZGF5X3Jvd10gPCBzbWEubG9uZ1tsYXN0ZGF5X3Jvd10gfCByc2kyX3ZhbHVlc1tsYXN0ZGF5X3Jvd10gPiA4MCAsIC0xLCAxKQopCgpwcmVkaWN0aW9uX29wdCA8LSBpaWYodm9sLnJhbmtbbGFzdGRheV9yb3ddID4gb3B0aW1pemVkLnBhcnNfNWRheXMkdm9sLnJhbmtfTUlOW2xhc3RkYXlfcm93XSwgCiAgICAgICAgICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXNbbGFzdGRheV9yb3ddIDwgb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUFYW2xhc3RkYXlfcm93XSAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0W2xhc3RkYXlfcm93XSA8IHNtYS5sb25nW2xhc3RkYXlfcm93XSB8IHJzaTJfdmFsdWVzID4gb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUlOW2xhc3RkYXlfcm93XSwgLTEsIDEpCikKCiMgRml4ZWQgcGFyYW1ldGVyIHN3aXRjaGluZyBmYWlscyB0byBwcmVkaWN0IGNvcnJlY3RseSBidXQgNSBkYXkgdXBkYXRlIHBhcmFtZXRlciB1cGRhdGUgZG9lcyBjb3JyZWN0bHkgcHJlZGljdCAtMQpwcmludChwYXN0ZTAoIlByZWRpY3Rpb24gZm9yIG5leHQgZGF5OiAgIixwcmVkaWN0aW9uX29wdCkpCmBgYAoKVGhpcyB3YXMgYWxzbyBldmlkZW50IGluIHRoZSBleHBsb3JhdGlvbiBwbG90cyBmb3Igd2Vla2RheSBzcGVjaWZpYyB0cmVuZHMgb24gcmV0dXJucwoKIyMgU2VjdGlvbiBJSUk6IE1vZGVsIERldmVsb3BtZW50IGFuZCBhbmFseXNpczogUGFydCBJSQojIyAgV2Vla2RheSBTcGVjaWZpYyBVcGRhdGUKCltVc2luZyB3ZWVrZGF5IHNwZWNpZmljIGRhaWx5IHJldHVybiB2b2xhdGlsaXR5ICh3ZWVrbHkgYmFycykgZm9yIFRGIGFuZCBNUiBzd2l0Y2hpbmddCgpgYGB7cn0KIyMgRXhwbG9pdGluZyB3ZWVrIG9mIHRoZSBkYXkgcGF0dGVybnMgaW4gcmV0dXJucwptYXJrZXRjbG9zZV93ZWVrZGF5IDwtIGJhc2U6OndlZWtkYXlzKGRhdGVfdHMsYWJicmV2aWF0ZT1UUlVFKQoKIyMgTW9kZWxpbmcgTW9uZGF5IHJldHVybnMKbWFya2V0Y2xvc2VfcmV0X01vbiA8LSBtYXJrZXRjbG9zZV9yZXRbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0KcGxvdCh4PWRhdGVfdHNbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0seT1hcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikseWxpbT1jKC0xLjUsMS41KSwKICAgICB4bGFiPSJZZWFyIix5bGFiID0gIiUgQ2hhbmdlIix0eXBlID0gJ2wnLGNvbD0icGluayIsCiAgICAgbWFpbj0iJSBXZWVrbHkgY2hhbmdlIGluIE1vbmRheSBkYWlseSBsb2cgcmV0dXJucyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikgfiBkYXRlX3RzW21hcmtldGNsb3NlX3dlZWtkYXkgPT0gIk1vbiJdICxmPTEvMiksY29sPSJyZWQiLGx3ZD0xKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldF9Nb24pICB+IGRhdGVfdHNbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0sZj0xLzQpLGNvbD0iYmx1ZSIsbHdkPTEpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikgIH4gZGF0ZV90c1ttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSxmPTEvOCksY29sPSJncmVlbiIsbHdkPTEpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikgIH4gZGF0ZV90c1ttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSxmPTEvMTIpLGNvbD0iYmxhY2siLGx3ZD0xKQpncmlkKGNvbD0xLGx3ZD0xKQpsZWdlbmQoeCA9ICJib3R0b21sZWZ0IixsZWdlbmQ9YygiJSBsb2cgcmV0IiwgImY9MS8yIGxvd2VzcyIsImY9MS80IiwgImY9MS84IiwiZj0xLzIiKSwKICAgICAgIGZpbGwgPSBjKCJwaW5rIiwicmVkIiwiYmx1ZSIsImdyZWVuIiwiYmxhY2siKSwgYnR5PSduJykKYGBgCgoKYGBge3J9CiMjIE1vbmRheSByZXR1cm5zIGNsZWFybHkgc2hvdyBhIHZpc2libGUgcGF0dGVybiwgd2UgY2FuIGNyZWF0ZSBhIHRpbWUgc2VyaWVzIGZvciByZXR1cm5zIGFuZCB1c2UgaXRzIHByZWRpY3RhYmlsaXR5IHRvIGltcHJvdmUgdGhlIGVhcmxpZXIgZm9yZWNhc3QKIyMgU3dpdGNoaW5nIHN0cmF0ZWd5IGJhc2VkIG9uIE1vbmRheSByZXR1cm5zOgoKIyMgTWVhbiByZXZlcnNpb24KcnNpMl92YWx1ZXNfTW9uID0gUlNJKG1hcmtldGNsb3NlX3JldF9Nb24vMTAwLDIpCgojIyBUcmVuZCBmb2xsb3dpbmcKc21hLnNob3J0X01vbiA8LSBTTUEobWFya2V0Y2xvc2VfcmV0X01vbi8xMDAsIDIpCnNtYS5sb25nX01vbiA8LSAgU01BKG1hcmtldGNsb3NlX3JldF9Nb24vMTAwLCA1KQoKIyMgVm9sYXRpbGl0eSBpbiB3ZWVrbHkgYmFycyBvZiBkYWlseSByZXR1cm5zIG9uIE1vbmRheXMKIyBOb3RlIHRoYXQgd2UgaGFkIG11bHRpcGxpZWQgdGhlIGNoYW5nZXMgYnkgMTAwIHdoZW4gY2FsY3VsYXRpbmcgYG1hcmtldGNsb3NlX3JldGAKbGlicmFyeShxdWFudG1vZCkKcmV0LmxvZ19Nb24gPSBtYXJrZXRjbG9zZV9yZXRfTW9uLzEwMApoaXN0LnZvbF9Nb24gPSBydW5TRChyZXQubG9nX01vbiwgbiA9IDUpCnZvbC5yYW5rX01vbiA9IHBlcmNlbnQucmFuayhTTUEocGVyY2VudC5yYW5rKGhpc3Qudm9sX01vbiwgNTIpLCA1KSwgNTApCgpgYGAKCgoKYGBge3J9CiMjIFN3aXRjaGluZyBzdHJhdGVneSBmb3IgTW9uZGF5IHJldHVybnMKc2lnX01vbl9yZXQgPC0gIChMYWcoTGFnKGlpZih2b2wucmFua19Nb24gPiAwLjUwLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGlpZigocnNpMl92YWx1ZXNfTW9uIDwgNTApICwgMSwgLTEpLAogICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydF9Nb24gPCBzbWEubG9uZ19Nb24gfCAoKHJzaTJfdmFsdWVzX01vbiA+IDgwKSksIC0xLCAxKQopKSkpCgojIyBDdW11bGF0aXZlIFBOTCBwbG90cyBmb3IgcHJlZGljdGluZyBkYWlseSByZXR1cm5zIG9uIE1vbmRheXMKcmV0X01vbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cylbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0qc2lnX01vbl9yZXQKZXFfTW9uIDwtIChjdW1zdW0ocmV0X01vblstYygxLDIpXSkpCnhfdmFyX01vbiA8LSAoZGF0ZV90c1ttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSlbLWMoMSwyKV0KcGxvdChhcy5udW1lcmljKGVxX01vbil+eF92YXJfTW9uLHR5cGU9J2wnLHhsYWI9IlllYXIiLHlsYWI9IkN1bXVsYXRpdmUgcmV0dXJucyBvbiBNb25kYXlzIiwKICAgICB5bGltPWMoLTEwMDAwLDMwMDAwKSkKCmVxX29wdF9Nb24gPC0gYXMubnVtZXJpYyggY3Vtc3VtKCAocmV0X29wdFttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSlbLWMoMSwyKV0pICkKbGluZXMoZXFfb3B0X01vbiB+IHhfdmFyX01vbixjb2w9ImdyZWVuIikKCmVnX2dpdmVuU3RyIDwtIGFzLm51bWVyaWMoIGN1bXN1bSggKDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKVttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSlbLWMoMSwyKV0pICkKbGluZXMoZWdfZ2l2ZW5TdHIgfiB4X3Zhcl9Nb24sY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xKQpsZWdlbmQoeD0idG9wbGVmdCIsbGVnZW5kPWMoIk1vbmRheSBwcmVkaWN0aW9uIiwiQ29tYmluZWQgc3dpdGNoaW5nIiwiQnV5IGV2ZXJ5IE1vbmRheSAoR2l2ZW4pIiksCiAgICAgICBmaWxsPWMoImJsYWNrIiwiZ3JlZW4iLCJyZWQiKSkKYGBgCgoKYGBge3J9CiMjIE9wdGltaXppbmcgcGFyYW1ldGVycwoKIyBDcmVhdGUgc3Vic2V0IGFycmF5cyB0byB0ZXN0IGhvdyBtdWNoIHRoZXNlIHBhcmFtZXRlcnMgY2hhbmdlIG92ZXIgdGltZQojIEV2ZXJ5IDEwIHdlZWsgdXBkYXRlIG9mIHBhcmFtZXRlcnMKc3ViX3Nlci5sZW4uTW9uIDwtIDUKc3RhcnRfcG9pbnQuTW9uIDwtIDE0MApUbWF4X2FycmF5Lk1vbiA8LSBzZXEoc3RhcnRfcG9pbnQuTW9uLGxlbmd0aChtYXJrZXRjbG9zZV9yZXRfTW9uKSxzdWJfc2VyLmxlbi5Nb24pCgojIEdyaWQgc2VhcmNoIGZvciBwYXJhbWV0ZXJzIHRoYXQgbWF4aW1pemUgY3VtdWxhdGl2ZSByZXR1cm4gb3ZlciBzdWJzZXQgdGltZSBzZXJpZXMKIyBhbmQgc3RvcmUgdGhlIHByZWRpY3Rpb24gZm9yIG5leHQgZGF5CnJldHVybl9tYXgudGVzdC5Nb24gPC0gbWF0cml4KC1JbmYsbGVuZ3RoKFRtYXhfYXJyYXkuTW9uKSwxKQpwcmVkaWN0X2FycmF5LnRlc3QuTW9uIDwtIG1hdHJpeCgxLGxlbmd0aChUbWF4X2FycmF5Lk1vbiksMSkKbWF4X3Bhci50ZXN0Lk1vbiA8LSBtYXRyaXgoMCxsZW5ndGgoVG1heF9hcnJheS5Nb24pLDMpCmBgYAoKCmBgYHtyfQojIE9yIGRpcmVjdGx5IHJlYWQgZnJvbSBoZXJlCm1heF9wYXIudGVzdC5Nb24gPC0gcmVhZC5jc3YoIm1heF9wYXIudGVzdC5Nb24uY3N2IilbLGMoIlYxIiwiVjIiLCJWMyIpXQpgYGAKCgpgYGB7cn0KcHJpbnQoIlN0YXJ0aW5nIGdyaWQgc2VhcmNoLiBUaGlzIG1pZ2h0IHRha2UgYSB3aGlsZSAoNS0xMCBtaW51dGVzKSIpCmZvcmVhY2ggKGkgPSAgMTpsZW5ndGgoVG1heF9hcnJheS5Nb24pKSAlZG8lIHsKICBwcmludChwYXN0ZSgiVXBkYXRpbmcgdGhyZXNob2xkIHBhcmFtZXRlcnMgZm9yIHRoZSA1IHdlZWsgTW9uZGF5IHRpbWUgc2VyaWVzIixpLCJvZiIsbGVuZ3RoKFRtYXhfYXJyYXkuTW9uKSkpCiAgVG1heC5Nb24gPC0gVG1heF9hcnJheS5Nb25baV0KICBtYXJrZXRjbG9zZV9yZXRfTW9uLnRlc3QgPC0gbWFya2V0Y2xvc2VfcmV0X01vblsxOlRtYXguTW9uXQogICMgRGlmZmVyZW5jZXMgaW4gcmV0dXJucyBvbiBNb25kYXlzCiAgbWFya2V0Y2xvc2VfcmV0X3JldF9Nb24udGVzdCA8LSAxMDAqZGlmZigobWFya2V0Y2xvc2VfcmV0X01vbi50ZXN0KSlbLWMoMSldCiAgCiAgIyMgVm9sYXRpbGl0eSBpbiB3ZWVrbHkgcmV0dXJucwogIHJldC5sb2cudGVzdC5Nb24gPC0gbWFya2V0Y2xvc2VfcmV0X01vbi50ZXN0LzEwMAogIGhpc3Qudm9sLnRlc3QuTW9uIDwtIHJ1blNEKHJldC5sb2cudGVzdC5Nb24sIG4gPSA1KQogIHZvbC5yYW5rLnRlc3QuTW9uIDwtIHBlcmNlbnQucmFuayhTTUEocGVyY2VudC5yYW5rKGhpc3Qudm9sLnRlc3QuTW9uLCA1MiksIDUpLCA1MCkKICAKICAjIyBNZWFuIHJldmVyc2lvbgogIHJzaTJfdmFsdWVzLnRlc3QuTW9uID0gUlNJKG1hcmtldGNsb3NlX3JldF9Nb24udGVzdC8xMDAsMikKICAjIyBUcmVuZCBmb2xsb3dpbmcKICBzbWEuc2hvcnQudGVzdC5Nb24gPC0gIFNNQShtYXJrZXRjbG9zZV9yZXRfTW9uLnRlc3QvMTAwLCAyKQogIHNtYS5sb25nLnRlc3QuTW9uIDwtIFNNQShtYXJrZXRjbG9zZV9yZXRfTW9uLnRlc3QvMTAwLCA1KQogIAogICMgR3JpZCBzZWFyY2ggZm9yIE1vbmRheSBwYXJhbWV0ZXJzCiAgZm9yZWFjaCAodm9sLnJhbmsuTW9uX01JTiA9IHNlcSgwLjMsMC44LDAuMDEpKSAlZG8lIHsKICAgIGZvcmVhY2ggKHJzaTJfdmFsdWVzLk1vbl9NQVggPSBzZXEoMzAsODAsMikpICVkbyUgewogICAgICBmb3IgKHJzaTJfdmFsdWVzLk1vbl9NSU4gaW4gNjApeyAjc2VxKDMwLDgwLDUpKSAgJWRvJXsgIyBkb2Vzbid0IGNoYW5nZQogICAgICAgIHNpZy50ZXN0Lk1vbiA8LSAgKGlpZih2b2wucmFuay50ZXN0Lk1vbiA+IHZvbC5yYW5rLk1vbl9NSU4sIAogICAgICAgICAgICAgICAgICAgICAgICAgIGlpZigocnNpMl92YWx1ZXMudGVzdC5Nb24gPCByc2kyX3ZhbHVlcy5Nb25fTUFYKSAsIDEsIC0xKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0LnRlc3QuTW9uIDwgc21hLmxvbmcudGVzdC5Nb24gfCAoKHJzaTJfdmFsdWVzLnRlc3QuTW9uID4gcnNpMl92YWx1ZXMuTW9uX01JTikpLCAtMSwgMSkKICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICApKQogICAgICAgIHByZWRpY3QudGVzdC5Nb24gPC0gdGFpbChzaWcudGVzdC5Nb24sMSkKICAgICAgICBzaWcudGVzdC5Nb24gPC0gTGFnKExhZyhzaWcudGVzdC5Nb24pKQogICAgICAgIHJldC50ZXN0Lk1vbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cylbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0qc2lnLnRlc3QuTW9uCiAgICAgICAgI3JldC50ZXN0Lk1vbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3JldF9Nb24udGVzdCkqc2lnLnRlc3QuTW9uCiAgICAgICAgCiAgICAgICAgcmV0dXJuX3RvdGFsLnRlc3QuTW9uIDwtIHRhaWwoY3Vtc3VtKHJldC50ZXN0Lk1vblstYygxLDIpXSksMSkKICAgICAgICAKICAgICAgICAjT3B0aW1pemUgY3VtdWxhdGl2ZSByZXR1cm4gb24gTW9uZGF5cwogICAgICAgIGlmIChyZXR1cm5fdG90YWwudGVzdC5Nb24gPiByZXR1cm5fbWF4LnRlc3QuTW9uW2ldKSB7CiAgICAgICAgICAKICAgICAgICAgIHJldHVybl9tYXgudGVzdC5Nb25baV0gPC0gcmV0dXJuX3RvdGFsLnRlc3QuTW9uCiAgICAgICAgICBtYXhfcGFyLnRlc3QuTW9uW2ksMV0gPC0gdm9sLnJhbmsuTW9uX01JTgogICAgICAgICAgbWF4X3Bhci50ZXN0Lk1vbltpLDJdIDwtIHJzaTJfdmFsdWVzLk1vbl9NQVgKICAgICAgICAgIG1heF9wYXIudGVzdC5Nb25baSwzXSA8LSByc2kyX3ZhbHVlcy5Nb25fTUlOCiAgICAgICAgICBwcmVkaWN0X2FycmF5LnRlc3QuTW9uW2ldIDwtIHByZWRpY3QudGVzdC5Nb24KICAgICAgICAgIHNpZy50ZXN0Lk1vbi5tYXggPC0gc2lnLnRlc3QuTW9uCiAgICAgICAgICAKICAgICAgICB9CiAgICAgICAgCiAgICAgIH0KICAgICAgCiAgICB9CiAgICAKICB9Cn0KCgojd3JpdGUuY3N2KG1heF9wYXIudGVzdC5Nb24sIm1heF9wYXIudGVzdC5Nb24uY3N2IikKYGBgCgpgYGB7cn0KcHJpbnQobWF4X3Bhci50ZXN0Lk1vbikKYGBgCgpgYGB7cn0KIyBXZSB1c2UgdGhlIG9wdGltYWwgcGFyYW1ldGVycyBmb3IgTW9uZGF5IHVwZGF0ZSAoMC40NCw2Miw2MCksIHRvIGNvcnJlY3QgdGhlIGVhcmxpZXIgbW9kZWwKIyMgU3dpdGNoaW5nIHN0cmF0ZWd5IGZvciBNb25kYXkgcmV0dXJucwpzaWdfb3B0Lk1vbiA8LSAgTGFnKExhZyhpaWYodm9sLnJhbmtfTW9uID4gMC40NCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKChyc2kyX3ZhbHVlc19Nb24gPCA2MikgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydF9Nb24gPCBzbWEubG9uZ19Nb24gfCAoKHJzaTJfdmFsdWVzX01vbiA+IDYwKSksIC0xLCAxKQopKSkKCgojI1Bsb3Qgb2YgY3VtdWxhdGl2ZSByZXR1cm5zIG9uIE1vbmRheXMgYWZ0ZXIgYW5kIGJlZm9yZSB1cGRhdGUKcGxvdChhcy5udW1lcmljKGVxX01vbil+eF92YXJfTW9uLHR5cGU9J2wnLHhsYWI9IlllYXIiLHlsYWI9IkN1bXVsYXRpdmUgcmV0dXJucyBvbiBNb25kYXlzIiwKICAgICB5bGltPWMoLTEwMDAwLDMwMDAwKSkKbGluZXMoZXFfb3B0X01vbiB+IHhfdmFyX01vbixjb2w9ImdyZWVuIikKbGluZXMoZWdfZ2l2ZW5TdHIgfiB4X3Zhcl9Nb24sY29sPSJyZWQiKQoKcmV0X01vbl9vcHQgPC0gMTAwMCptb21lbnR1bShtYXJrZXRjbG9zZV94dHMpW21hcmtldGNsb3NlX3dlZWtkYXkgPT0gIk1vbiJdKnNpZ19vcHQuTW9uCmVxX01vbl9vcHQgPC0gYXMubnVtZXJpYyggY3Vtc3VtKCAocmV0X01vbl9vcHQpWy1jKDEsMildKSApCmxpbmVzKGVxX01vbl9vcHQgfiB4X3Zhcl9Nb24sY29sPSJibHVlIikKCmdyaWQoY29sPTEsbHdkPTEpCmxlZ2VuZCh4PSJ0b3BsZWZ0IixsZWdlbmQ9YygiNSB3ZWVrIE1vbmRheSB1cGRhdGUgKyA1IGRheSB1cGRhdGUiLCJNb25kYXkgcHJlZGljdGlvbiIsIjUgZGF5IHVwZGF0ZSIsIkJ1eSBldmVyeSBNb25kYXkgKEdpdmVuKSIpLAogICAgICAgZmlsbD1jKCJibHVlIiwiYmxhY2siLCJncmVlbiIsInJlZCIpKQoKCmBgYAoKYGBge3J9CiMgQ29ycmVjdGluZyB0aGUgZWFybGllciBwbG90cwpzaWdfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbiA8LSBzaWdfb3B0X29wdGltYWwKc2lnX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb25bbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl1bLWMoMSwyKV0gPC0gc2lnX29wdC5Nb25bLWMoMSwyKV0KCnJldF9vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uIDwtIDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKVstYygxLDIpXSpzaWdfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbgplcV9vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uIDwtIGFzLm51bWVyaWMoIGN1bXN1bSggKHJldF9vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uKSApKQpgYGAKCgpgYGB7cn0KI0N1bXVsYXRpdmUgUE5MIHBsb3RzIGZvciBjb21wbGV0ZSBzZXJpZXMgYWZ0ZXIgbW9uZGF5IGNvcnJlY3Rpb24KcGxvdChjKE5BLGVxX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb24pfmRhdGVfdHNbLWMoMSldLGNvbD0ib3JhbmdlIix0eXBlPSdsJywgeWxpbT1jKDAsMTQwMDAwKSx4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpsaW5lcyhjb3JlZGF0YShlcV9vcHRfb3B0aW1hbCl+ZGF0ZV90c1stYygxKV0sY29sPSJibHVlIikKbGluZXMoY29yZWRhdGEoZXFfb3B0KX5kYXRlX3RzWy1jKDEpXSxjb2w9ImdyZWVuIikKbGluZXMoY29yZWRhdGEoZXEpfmRhdGVfdHNbLWMoMSldKQpsaW5lcyhkYXRhX2luJENVTVNVTVstYygxKV1+ZGF0ZV90c1stYygxKV0sY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xKQpsZWdlbmQoeD0idG9wbGVmdCIsbGVnZW5kPWMoIjVkYXkgdXBkYXRlIHN3IiwiRml4ZWQgcGFyIHN3aXRjaGluZyIsIkJ1eSBEYWlseSAoR2l2ZW4pIiwiT3B0aW1hbCIsIk9wdGltYWwrTW9uZGF5IHVwZGF0ZXMiKSwKICAgICAgIGZpbGw9YygiZ3JlZW4iLCJibGFjayIsInJlZCIsImJsdWUiLCJvcmFuZ2UiKSkKYGBgCgpgYGB7cn0KIyBTaW1pbGFybHkgd2UgY2FuIGNoZWNrIGZvciB3ZWVrZGF5IHBhdHRlcm5zIG9uIG90aGVyIGRheXMgYW5kIGFsc28gcG90ZW50aWFsIG1vbnRobHkgb3IgcXVhcnRlcmx5IHBhdHRlcm5zCgojIyBTaW5jZSBuZXh0IGRheSBvbiB3aGljaCBwcmVkaWN0aW9uIGlzIG5lZWRlZCBhbHNvIGZhbGxzIG9uIGEgTW9uZGF5LCB3ZSBjYW4gYWxzbyB2ZXJpZnkgdGhlIHByZWRpY3Rpb24gZnJvbSBNb25kYXkgc2VyaWVzIG9ubHk6Cmxhc3R3ZWVrX3Jvdy5Nb24gPC0gbGVuZ3RoKG1hcmtldGNsb3NlX3JldF9Nb24pCnByZWRpY3Rpb25fZml4ZWQuTW9uIDwtIGlpZih2b2wucmFua19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPiAwLjUwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlpZihyc2kyX3ZhbHVlc19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPCA1MCAsIDEsIC0xKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlpZigoc21hLnNob3J0X01vbltsYXN0d2Vla19yb3cuTW9uXSA8IHNtYS5sb25nX01vbltsYXN0d2Vla19yb3cuTW9uXSkgfCAocnNpMl92YWx1ZXNfTW9uW2xhc3R3ZWVrX3Jvdy5Nb25dID4gODApICwgLTEsIDEpCikKCnByZWRpY3Rpb25fb3B0IDwtIGlpZih2b2wucmFua19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPiAwLjQ0LCAKICAgICAgICAgICAgICAgICAgICAgIGlpZihyc2kyX3ZhbHVlc19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPCA2MiAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICBpaWYoKHNtYS5zaG9ydF9Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPCBzbWEubG9uZ19Nb25bbGFzdHdlZWtfcm93Lk1vbl0pICB8IChyc2kyX3ZhbHVlc19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPiA2MCksIC0xLCAxKQopCgojIEFnYWluIGZpeGVkIHBhcmFtZXRlciBzd2l0Y2hpbmcgZmFpbHMgdG8gcHJlZGljdCBjb3JyZWN0bHkgYnV0IDIgd2VlayBwYXJhbWV0ZXIgdXBkYXRlIGRvZXMgY29ycmVjdGx5IHByZWRpY3QgLTEKcHJpbnQocGFzdGUwKCJQcmVkaWN0aW9uIGZvciBuZXh0IGRheSAoMiB3ZWVrIHBhcmFtZXRlciB1cGRhdGUpOiAgIixwcmVkaWN0aW9uX29wdCkpCgpgYGAKCmBgYHtyfQpDb21wYXJlIGN1bXVsYXRpdmUgUE5MIGF0IHRoZSBlbmQgYWZ0ZXIgTW9uZGF5IGNvcnJlY3Rpb24KcHJpbnQoZGF0YS5mcmFtZSgnR2l2ZW4gU3RyYXRlZ3knPXRhaWwoZGF0YV9pbiRDVU1TVU1bLWMoMSldKSwnRml4UGFyU3dpdGNoaW5nJz10YWlsKGFzLnZlY3RvcihlcSkpLCc1ZGF5VXBkYXRlU3cnPXRhaWwoYXMudmVjdG9yKGVxX29wdCkpLCdPcHRpbWFsJz10YWlsKGFzLnZlY3RvcihlcV9vcHRfb3B0aW1hbCkpLCdPcHRpbWFsJk1vblVwZGF0ZSc9dGFpbChhcy52ZWN0b3IoZXFfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbikpLHJvdy5uYW1lcyA9IGFzLmNoYXJhY3Rlcih0YWlsKGRhdGVfdHMpKSkpCgp3cml0ZS5jc3YoY2JpbmQoInNpZ19vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uIj1hcy5udW1lcmljKHNpZ19vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uKSwic2lnX29wdF9vcHRpbWFsIj1hcy5udW1lcmljKHNpZ19vcHRfb3B0aW1hbCksInNpZ19vcHQiPWFzLm51bWVyaWMoc2lnX29wdCksJ3NpZyc9YXMubnVtZXJpYyhzaWcpKSwidHJhZGluZ19zaWduYWxzLmNzdiIscXVvdGU9RkFMU0Uscm93Lm5hbWVzID0gZGF0ZV90cykKYGBgCgo=